├── .github ├── FUNDING.yml └── coverage │ ├── badge.svg │ └── coverage.txt ├── LICENSE ├── README.md ├── etw ├── advapi32.go ├── advapi32_exports.go ├── advapi32_header.go ├── advapi32_helper.go ├── advapi32_test.go ├── autologger.go ├── autologger_test.go ├── consumer.go ├── errors.go ├── etw_helpers.go ├── etw_test.go ├── event.go ├── filter.go ├── guid.go ├── guid_test.go ├── kernel_provider_test.go ├── kernel_providers.go ├── mof.go ├── producer.go ├── provider.go ├── tdh.go ├── tdh_exports.go ├── tdh_headers.go ├── utils.go └── utils_test.go ├── examples ├── run.sh └── simple │ └── simple.go ├── go.mod ├── go.sum └── tools └── etwdump ├── etwdump.go ├── filemon.go └── makefile /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [0xrawsec] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/coverage/badge.svg: -------------------------------------------------------------------------------- 1 | coverage: 74.6%coverage74.6% -------------------------------------------------------------------------------- /.github/coverage/coverage.txt: -------------------------------------------------------------------------------- 1 | ok github.com/0xrawsec/golang-etw/etw 73.183s coverage: 74.6% of statements 2 | github.com/0xrawsec/golang-etw/etw/advapi32.go:18: StartTrace 75.0% 3 | github.com/0xrawsec/golang-etw/etw/advapi32.go:40: StopTrace 0.0% 4 | github.com/0xrawsec/golang-etw/etw/advapi32.go:66: EnableTraceEx2 75.0% 5 | github.com/0xrawsec/golang-etw/etw/advapi32.go:97: ProcessTrace 75.0% 6 | github.com/0xrawsec/golang-etw/etw/advapi32.go:117: OpenTrace 75.0% 7 | github.com/0xrawsec/golang-etw/etw/advapi32.go:135: ControlTrace 75.0% 8 | github.com/0xrawsec/golang-etw/etw/advapi32.go:155: CloseTrace 75.0% 9 | github.com/0xrawsec/golang-etw/etw/advapi32.go:171: EventAccessQuery 100.0% 10 | github.com/0xrawsec/golang-etw/etw/advapi32.go:194: EventAccessControl 0.0% 11 | github.com/0xrawsec/golang-etw/etw/advapi32.go:228: ConvertSecurityDescriptorToStringSecurityDescriptorW 80.0% 12 | github.com/0xrawsec/golang-etw/etw/advapi32.go:260: ConvertStringSidToSidW 87.5% 13 | github.com/0xrawsec/golang-etw/etw/advapi32_header.go:351: NewEventTraceSessionProperties 100.0% 14 | github.com/0xrawsec/golang-etw/etw/advapi32_header.go:357: NewRealTimeEventTraceSessionProperties 100.0% 15 | github.com/0xrawsec/golang-etw/etw/advapi32_header.go:454: AllocEventFilterEventID 100.0% 16 | github.com/0xrawsec/golang-etw/etw/advapi32_header.go:470: Size 100.0% 17 | github.com/0xrawsec/golang-etw/etw/advapi32_header.go:542: SetProcessTraceMode 100.0% 18 | github.com/0xrawsec/golang-etw/etw/advapi32_header.go:583: ExtendedDataItem 66.7% 19 | github.com/0xrawsec/golang-etw/etw/advapi32_header.go:590: RelatedActivityID 100.0% 20 | github.com/0xrawsec/golang-etw/etw/advapi32_header.go:601: GetEventInformation 100.0% 21 | github.com/0xrawsec/golang-etw/etw/advapi32_header.go:666: GetMapInfo 0.0% 22 | github.com/0xrawsec/golang-etw/etw/advapi32_header.go:690: PointerSize 66.7% 23 | github.com/0xrawsec/golang-etw/etw/advapi32_header.go:753: UTCTimeStamp 100.0% 24 | github.com/0xrawsec/golang-etw/etw/advapi32_helper.go:10: GetAccessString 88.9% 25 | github.com/0xrawsec/golang-etw/etw/autologger.go:28: hexStr 100.0% 26 | github.com/0xrawsec/golang-etw/etw/autologger.go:46: Path 100.0% 27 | github.com/0xrawsec/golang-etw/etw/autologger.go:50: Create 71.4% 28 | github.com/0xrawsec/golang-etw/etw/autologger.go:74: binaryFilter 0.0% 29 | github.com/0xrawsec/golang-etw/etw/autologger.go:82: EnableProvider 57.1% 30 | github.com/0xrawsec/golang-etw/etw/autologger.go:122: Exists 100.0% 31 | github.com/0xrawsec/golang-etw/etw/autologger.go:126: Delete 100.0% 32 | github.com/0xrawsec/golang-etw/etw/autologger.go:130: execute 80.0% 33 | github.com/0xrawsec/golang-etw/etw/autologger.go:139: regAddValue 100.0% 34 | github.com/0xrawsec/golang-etw/etw/consumer.go:20: SessionSlice 100.0% 35 | github.com/0xrawsec/golang-etw/etw/consumer.go:81: NewRealTimeConsumer 100.0% 36 | github.com/0xrawsec/golang-etw/etw/consumer.go:96: bufferCallback 100.0% 37 | github.com/0xrawsec/golang-etw/etw/consumer.go:106: callback 80.8% 38 | github.com/0xrawsec/golang-etw/etw/consumer.go:166: newRealTimeLogfile 100.0% 39 | github.com/0xrawsec/golang-etw/etw/consumer.go:179: close 90.0% 40 | github.com/0xrawsec/golang-etw/etw/consumer.go:203: OpenTrace 75.0% 41 | github.com/0xrawsec/golang-etw/etw/consumer.go:222: FromSessions 100.0% 42 | github.com/0xrawsec/golang-etw/etw/consumer.go:233: FromTraceNames 100.0% 43 | github.com/0xrawsec/golang-etw/etw/consumer.go:241: InitFilters 100.0% 44 | github.com/0xrawsec/golang-etw/etw/consumer.go:249: DefaultEventRecordCallback 100.0% 45 | github.com/0xrawsec/golang-etw/etw/consumer.go:256: DefaultEventCallback 85.7% 46 | github.com/0xrawsec/golang-etw/etw/consumer.go:279: Start 81.8% 47 | github.com/0xrawsec/golang-etw/etw/consumer.go:305: Err 100.0% 48 | github.com/0xrawsec/golang-etw/etw/consumer.go:311: Stop 100.0% 49 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:40: maxu32 66.7% 50 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:47: Parseable 100.0% 51 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:51: Value 100.0% 52 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:62: parse 57.7% 53 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:137: newEventRecordHelper 80.0% 54 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:148: initialize 100.0% 55 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:157: setEventMetadata 100.0% 56 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:189: endUserData 100.0% 57 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:193: userDataLength 100.0% 58 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:197: getPropertyLength 68.2% 59 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:240: getPropertySize 100.0% 60 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:248: getArraySize 86.7% 61 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:271: prepareProperty 85.7% 62 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:297: prepareProperties 83.8% 63 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:372: buildEvent 83.3% 64 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:386: parseAndSetProperty 0.0% 65 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:438: shouldParse 50.0% 66 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:446: parseAndSetAllProperties 56.7% 67 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:517: SelectFields 0.0% 68 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:523: ProviderGUID 100.0% 69 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:527: Provider 100.0% 70 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:531: Channel 100.0% 71 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:535: EventID 100.0% 72 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:539: GetPropertyString 100.0% 73 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:548: GetPropertyInt 75.0% 74 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:558: GetPropertyUint 75.0% 75 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:568: SetProperty 50.0% 76 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:578: ParseProperties 75.0% 77 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:588: ParseProperty 33.3% 78 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:622: Skippable 100.0% 79 | github.com/0xrawsec/golang-etw/etw/etw_helpers.go:626: Skip 100.0% 80 | github.com/0xrawsec/golang-etw/etw/event.go:58: NewEvent 100.0% 81 | github.com/0xrawsec/golang-etw/etw/event.go:66: GetProperty 42.9% 82 | github.com/0xrawsec/golang-etw/etw/event.go:83: GetPropertyString 75.0% 83 | github.com/0xrawsec/golang-etw/etw/filter.go:29: matchKey 81.8% 84 | github.com/0xrawsec/golang-etw/etw/filter.go:60: NewProviderFilter 100.0% 85 | github.com/0xrawsec/golang-etw/etw/filter.go:67: Match 100.0% 86 | github.com/0xrawsec/golang-etw/etw/filter.go:72: Update 100.0% 87 | github.com/0xrawsec/golang-etw/etw/guid.go:39: IsZero 100.0% 88 | github.com/0xrawsec/golang-etw/etw/guid.go:43: String 100.0% 89 | github.com/0xrawsec/golang-etw/etw/guid.go:52: Equals 100.0% 90 | github.com/0xrawsec/golang-etw/etw/guid.go:71: MustParseGUIDFromString 75.0% 91 | github.com/0xrawsec/golang-etw/etw/guid.go:80: ParseGUID 82.8% 92 | github.com/0xrawsec/golang-etw/etw/kernel_providers.go:132: IsKernelProvider 75.0% 93 | github.com/0xrawsec/golang-etw/etw/kernel_providers.go:141: GetKernelProviderFlags 100.0% 94 | github.com/0xrawsec/golang-etw/etw/producer.go:35: NewRealTimeSession 100.0% 95 | github.com/0xrawsec/golang-etw/etw/producer.go:45: NewKernelRealTimeSession 100.0% 96 | github.com/0xrawsec/golang-etw/etw/producer.go:56: IsStarted 100.0% 97 | github.com/0xrawsec/golang-etw/etw/producer.go:61: Start 45.5% 98 | github.com/0xrawsec/golang-etw/etw/producer.go:87: EnableProvider 81.2% 99 | github.com/0xrawsec/golang-etw/etw/producer.go:135: TraceName 100.0% 100 | github.com/0xrawsec/golang-etw/etw/producer.go:140: Providers 100.0% 101 | github.com/0xrawsec/golang-etw/etw/producer.go:145: Stop 100.0% 102 | github.com/0xrawsec/golang-etw/etw/provider.go:34: IsZero 100.0% 103 | github.com/0xrawsec/golang-etw/etw/provider.go:38: eventIDFilterDescriptor 100.0% 104 | github.com/0xrawsec/golang-etw/etw/provider.go:52: BuildFilterDesc 100.0% 105 | github.com/0xrawsec/golang-etw/etw/provider.go:60: MustParseProvider 100.0% 106 | github.com/0xrawsec/golang-etw/etw/provider.go:69: IsKnownProvider 100.0% 107 | github.com/0xrawsec/golang-etw/etw/provider.go:78: ParseProvider 63.9% 108 | github.com/0xrawsec/golang-etw/etw/provider.go:148: EnumerateProviders 100.0% 109 | github.com/0xrawsec/golang-etw/etw/provider.go:177: ResolveProvider 85.7% 110 | github.com/0xrawsec/golang-etw/etw/tdh.go:21: TdhEnumerateProviderFieldInformation 0.0% 111 | github.com/0xrawsec/golang-etw/etw/tdh.go:45: TdhEnumerateProviders 100.0% 112 | github.com/0xrawsec/golang-etw/etw/tdh.go:68: TdhGetEventInformation 100.0% 113 | github.com/0xrawsec/golang-etw/etw/tdh.go:95: TdhGetEventMapInformation 0.0% 114 | github.com/0xrawsec/golang-etw/etw/tdh.go:123: TdhGetProperty 75.0% 115 | github.com/0xrawsec/golang-etw/etw/tdh.go:156: TdhGetPropertySize 100.0% 116 | github.com/0xrawsec/golang-etw/etw/tdh.go:186: TdhQueryProviderFieldInformation 0.0% 117 | github.com/0xrawsec/golang-etw/etw/tdh.go:221: TdhFormatProperty 100.0% 118 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:222: pointer 100.0% 119 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:226: pointerOffset 0.0% 120 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:230: stringAt 100.0% 121 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:237: cleanStringAt 100.0% 122 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:245: EventMessage 100.0% 123 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:249: ProviderName 100.0% 124 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:253: TaskName 100.0% 125 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:257: LevelName 100.0% 126 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:261: OpcodeName 100.0% 127 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:265: KeywordName 100.0% 128 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:269: ChannelName 100.0% 129 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:274: ActivityIDName 100.0% 130 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:279: RelatedActivityIDName 100.0% 131 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:283: IsMof 100.0% 132 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:287: IsXML 100.0% 133 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:291: EventID 100.0% 134 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:303: GetEventPropertyInfoAt 80.0% 135 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:315: PropertyNameOffset 100.0% 136 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:370: GetEventMapEntryAt 0.0% 137 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:396: RemoveTrailingSpace 0.0% 138 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:520: InType 100.0% 139 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:523: StructStartIndex 100.0% 140 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:527: OutType 100.0% 141 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:531: NumOfStructMembers 100.0% 142 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:535: MapNameOffset 100.0% 143 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:539: CustomSchemaOffset 100.0% 144 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:543: Count 100.0% 145 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:547: CountPropertyIndex 100.0% 146 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:551: LengthPropertyIndex 100.0% 147 | github.com/0xrawsec/golang-etw/etw/tdh_headers.go:555: Length 100.0% 148 | github.com/0xrawsec/golang-etw/etw/utils.go:13: max 66.7% 149 | github.com/0xrawsec/golang-etw/etw/utils.go:22: UTF16BytesToString 100.0% 150 | github.com/0xrawsec/golang-etw/etw/utils.go:27: UTF16PtrToString 100.0% 151 | github.com/0xrawsec/golang-etw/etw/utils.go:31: Wcslen 100.0% 152 | github.com/0xrawsec/golang-etw/etw/utils.go:41: UTF16AtOffsetToString 100.0% 153 | github.com/0xrawsec/golang-etw/etw/utils.go:51: CopyData 100.0% 154 | github.com/0xrawsec/golang-etw/etw/utils.go:61: UUID 83.3% 155 | total: (statements) 74.6% 156 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 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 General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://pkg.go.dev/badge/github.com/0xrawsec/golang-etw)](https://pkg.go.dev/github.com/0xrawsec/golang-etw/etw?GOOS=windows) 2 | ![Version](https://img.shields.io/github/v/tag/0xrawsec/golang-etw?label=version) 3 | [![Coverage](https://raw.githubusercontent.com/0xrawsec/golang-etw/master/.github/coverage/badge.svg)](https://raw.githubusercontent.com/0xrawsec/golang-etw/master/.github/coverage/coverage.txt) 4 | 5 | Pure Golang (no need to enable CGO) library to consume ETW logs. 6 | 7 | ## Examples 8 | 9 | See [./examples](./examples) 10 | 11 | ```go 12 | package main 13 | 14 | import ( 15 | "context" 16 | "encoding/json" 17 | "fmt" 18 | "time" 19 | 20 | "github.com/0xrawsec/golang-etw/etw" 21 | ) 22 | 23 | func main() { 24 | // ETW needs a trace to be created before being able to consume from 25 | // it. Traces can be created using golang-etw or they might be already 26 | // existing (created from an autologgers for instance) like Eventlog-Security. 27 | 28 | // Creating the trace (producer part) 29 | s := etw.NewRealTimeSession("TestingGoEtw") 30 | 31 | // We have to stop the session or it will be kept alive and session name 32 | // will not be available anymore for next calls 33 | defer s.Stop() 34 | 35 | // we need to enable the trace to collect logs from given providers 36 | // several providers can be enabled per trace, in this example we 37 | // enable only one provider 38 | if err := s.EnableProvider(etw.MustParseProvider("Microsoft-Windows-Kernel-File")); err != nil { 39 | panic(err) 40 | } 41 | 42 | // Consuming from the trace 43 | c := etw.NewRealTimeConsumer(context.Background()) 44 | 45 | defer c.Stop() 46 | 47 | c.FromSessions(s) 48 | 49 | // When events are parsed they get sent to Consumer's 50 | // Events channel by the default EventCallback method 51 | // EventCallback can be modified to do otherwise 52 | go func() { 53 | var b []byte 54 | var err error 55 | for e := range c.Events { 56 | if b, err = json.Marshal(e); err != nil { 57 | panic(err) 58 | } 59 | fmt.Println(string(b)) 60 | } 61 | }() 62 | 63 | if err := c.Start(); err != nil { 64 | panic(err) 65 | } 66 | 67 | time.Sleep(5 * time.Second) 68 | 69 | if c.Err() != nil { 70 | panic(c.Err()) 71 | } 72 | 73 | } 74 | ``` 75 | 76 | ## Related Documentation 77 | 78 | - [Advanced ETWSession Configuration](https://docs.microsoft.com/en-us/message-analyzer/specifying-advanced-etw-session-configuration-settings) 79 | 80 | ## Related Work 81 | 82 | - [ETW Explorer](https://github.com/zodiacon/EtwExplorer) 83 | - [ETWProviders](https://github.com/repnz/etw-providers-docs) 84 | -------------------------------------------------------------------------------- /etw/advapi32.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | /* 12 | StartTraceW API wrapper generated from prototype 13 | EXTERN_C ULONG WMIAPI StartTraceW ( 14 | PTRACEHANDLE TraceHandle, 15 | LPCWSTR InstanceName, 16 | PEVENT_TRACE_PROPERTIES Properties); 17 | */ 18 | func StartTrace(traceHandle *syscall.Handle, 19 | instanceName *uint16, 20 | properties *EventTraceProperties) error { 21 | r1, _, _ := startTraceW.Call( 22 | uintptr(unsafe.Pointer(traceHandle)), 23 | uintptr(unsafe.Pointer(instanceName)), 24 | uintptr(unsafe.Pointer(properties))) 25 | if r1 == 0 { 26 | return nil 27 | } 28 | return syscall.Errno(r1) 29 | } 30 | 31 | /* 32 | StopTrace API wrapper generated from prototype 33 | EXTERN_C ULONG WMIAPI StopTrace( 34 | IN TRACEHANDLE TraceHandle, 35 | IN LPCWSTR InstanceName OPTIONAL, 36 | IN OUT PEVENT_TRACE_PROPERTIES Properties); 37 | 38 | NB: This function is obsolete. The ControlTrace function supersedes this function. 39 | */ 40 | func StopTrace( 41 | traceHandle syscall.Handle, 42 | instanceName *uint16, 43 | properties *EventTraceProperties) error { 44 | r1, _, _ := stopTraceW.Call( 45 | uintptr(traceHandle), 46 | uintptr(unsafe.Pointer(instanceName)), 47 | uintptr(unsafe.Pointer(properties))) 48 | if r1 == 0 { 49 | return nil 50 | } 51 | return syscall.Errno(r1) 52 | } 53 | 54 | /* 55 | EnableTraceEx2 API wrapper generated from prototype 56 | EXTERN_C ULONG WMIAPI EnableTraceEx2 ( 57 | TRACEHANDLE TraceHandle, 58 | LPCGUID ProviderId, 59 | ULONG ControlCode, 60 | UCHAR Level, 61 | ULONGLONG MatchAnyKeyword, 62 | ULONGLONG MatchAllKeyword, 63 | ULONG Timeout, 64 | PENABLE_TRACE_PARAMETERS EnableParameters); 65 | */ 66 | func EnableTraceEx2(traceHandle syscall.Handle, 67 | providerId *GUID, 68 | controlCode uint32, 69 | level uint8, 70 | matchAnyKeyword uint64, 71 | matchAllKeyword uint64, 72 | timeout uint32, 73 | enableParameters *EnableTraceParameters) error { 74 | r1, _, _ := enableTraceEx2.Call( 75 | uintptr(traceHandle), 76 | uintptr(unsafe.Pointer(providerId)), 77 | uintptr(controlCode), 78 | uintptr(level), 79 | uintptr(matchAnyKeyword), 80 | uintptr(matchAllKeyword), 81 | uintptr(timeout), 82 | uintptr(unsafe.Pointer(enableParameters))) 83 | if r1 == 0 { 84 | return nil 85 | } 86 | return syscall.Errno(r1) 87 | } 88 | 89 | /* 90 | ProcessTrace API wrapper generated from prototype 91 | EXTERN_C ULONG WMIAPI ProcessTrace ( 92 | PTRACEHANDLE HandleArray, 93 | ULONG HandleCount, 94 | LPFILETIME StartTime, 95 | LPFILETIME EndTime); 96 | */ 97 | func ProcessTrace(handleArray *syscall.Handle, 98 | handleCount uint32, 99 | startTime *FileTime, 100 | endTime *FileTime) error { 101 | r1, _, _ := processTrace.Call( 102 | uintptr(unsafe.Pointer(handleArray)), 103 | uintptr(handleCount), 104 | uintptr(unsafe.Pointer(startTime)), 105 | uintptr(unsafe.Pointer(endTime))) 106 | if r1 == 0 { 107 | return nil 108 | } 109 | return syscall.Errno(r1) 110 | } 111 | 112 | /* 113 | OpenTraceW API wrapper generated from prototype 114 | EXTERN_C TRACEHANDLE WMIAPI OpenTraceW ( 115 | PEVENT_TRACE_LOGFILEW Logfile); 116 | */ 117 | func OpenTrace(logfile *EventTraceLogfile) (syscall.Handle, error) { 118 | r1, _, err := openTraceW.Call( 119 | uintptr(unsafe.Pointer(logfile))) 120 | // This call stores error in lastError so we can keep it like this 121 | if err.(syscall.Errno) == 0 { 122 | return syscall.Handle(r1), nil 123 | } 124 | return syscall.Handle(r1), err 125 | } 126 | 127 | /* 128 | ControlTraceW API wrapper generated from prototype 129 | EXTERN_C ULONG WMIAPI ControlTraceW ( 130 | TRACEHANDLE TraceHandle, 131 | LPCWSTR InstanceName, 132 | PEVENT_TRACE_PROPERTIES Properties, 133 | ULONG ControlCode); 134 | */ 135 | func ControlTrace(traceHandle syscall.Handle, 136 | instanceName *uint16, 137 | properties *EventTraceProperties, 138 | controlCode uint32) error { 139 | r1, _, _ := controlTraceW.Call( 140 | uintptr(traceHandle), 141 | uintptr(unsafe.Pointer(instanceName)), 142 | uintptr(unsafe.Pointer(properties)), 143 | uintptr(controlCode)) 144 | if r1 == 0 { 145 | return nil 146 | } 147 | return syscall.Errno(r1) 148 | } 149 | 150 | /* 151 | CloseTrace API wrapper generated from prototype 152 | EXTERN_C ULONG WMIAPI CloseTrace ( 153 | TRACEHANDLE TraceHandle); 154 | */ 155 | func CloseTrace(traceHandle syscall.Handle) error { 156 | r1, _, _ := closeTrace.Call( 157 | uintptr(traceHandle)) 158 | if r1 == 0 { 159 | return nil 160 | } 161 | return syscall.Errno(r1) 162 | } 163 | 164 | /* 165 | EventAccessQuery API wrapper generated from prototype 166 | ULONG EVNTAPI EventAccessQuery ( 167 | LPGUID Guid, 168 | PSECURITY_DESCRIPTOR Buffer, 169 | PULONG BufferSize); 170 | */ 171 | func EventAccessQuery( 172 | guid *GUID, 173 | buffer *SecurityDescriptor, 174 | bufferSize *uint32) error { 175 | r1, _, _ := eventAccessQuery.Call( 176 | uintptr(unsafe.Pointer(guid)), 177 | uintptr(unsafe.Pointer(buffer)), 178 | uintptr(unsafe.Pointer(bufferSize))) 179 | if r1 == 0 { 180 | return nil 181 | } 182 | return syscall.Errno(r1) 183 | } 184 | 185 | /* 186 | EventAccessControl API wrapper generated from prototype 187 | ULONG EVNTAPI EventAccessControl ( 188 | LPGUID Guid, 189 | ULONG Operation, 190 | PSID Sid, 191 | ULONG Rights, 192 | BOOLEAN AllowOrDeny); 193 | */ 194 | func EventAccessControl(guid *GUID, 195 | operation uint32, 196 | sid *SID, 197 | rights uint32, 198 | allowOrDeny bool) error { 199 | 200 | // we need a byte not a bool 201 | var bAllowOrDeny byte 202 | 203 | if allowOrDeny { 204 | bAllowOrDeny = 1 205 | } 206 | 207 | r1, _, _ := eventAccessControl.Call( 208 | uintptr(unsafe.Pointer(guid)), 209 | uintptr(operation), 210 | uintptr(unsafe.Pointer(sid)), 211 | uintptr(rights), 212 | uintptr(bAllowOrDeny)) 213 | if r1 == 0 { 214 | return nil 215 | } 216 | return syscall.Errno(r1) 217 | } 218 | 219 | /* 220 | ConvertSecurityDescriptorToStringSecurityDescriptorW API wrapper generated from prototype 221 | WINADVAPI WINBOOL WINAPI ConvertSecurityDescriptorToStringSecurityDescriptorW( 222 | PSECURITY_DESCRIPTOR SecurityDescriptor, 223 | DWORD RequestedStringSDRevision, 224 | SECURITY_INFORMATION SecurityInformation, 225 | LPWSTR *StringSecurityDescriptor, 226 | PULONG StringSecurityDescriptorLen); 227 | */ 228 | func ConvertSecurityDescriptorToStringSecurityDescriptorW( 229 | securityDescriptor *SecurityDescriptor, 230 | requestedStringSDRevision uint32, 231 | securityInformation SecurityInformation, 232 | ) (string, error) { 233 | var stringSecurityDescriptor uint16 234 | var stringSecurityDescriptorLen uint32 235 | 236 | pStringSecurityDescriptor := &stringSecurityDescriptor 237 | 238 | _, _, err := convertSecurityDescriptorToStringSecurityDescriptorW.Call( 239 | uintptr(unsafe.Pointer(securityDescriptor)), 240 | uintptr(requestedStringSDRevision), 241 | uintptr(securityInformation), 242 | uintptr(unsafe.Pointer(&pStringSecurityDescriptor)), 243 | uintptr(unsafe.Pointer(&stringSecurityDescriptorLen))) 244 | if err == ERROR_SUCCESS { 245 | s := UTF16PtrToString(pStringSecurityDescriptor) 246 | if _, err := syscall.LocalFree(syscall.Handle(unsafe.Pointer(pStringSecurityDescriptor))); err != nil { 247 | return "", err 248 | } 249 | return s, nil 250 | } 251 | return "", err 252 | } 253 | 254 | /* 255 | ConvertStringSidToSidW API wrapper generated from prototype 256 | WINADVAPI WINBOOL WINAPI ConvertStringSidToSidW( 257 | LPCWSTR StringSid, 258 | PSID *Sid); 259 | */ 260 | func ConvertStringSidToSidW(stringSid string) (sid *SID, err error) { 261 | 262 | var rc uintptr 263 | var utf16Sid *uint16 264 | 265 | if utf16Sid, err = syscall.UTF16PtrFromString(stringSid); err != nil { 266 | return 267 | } 268 | 269 | rc, _, err = convertStringSidToSidW.Call( 270 | uintptr(unsafe.Pointer(utf16Sid)), 271 | uintptr(unsafe.Pointer(&sid))) 272 | 273 | if rc != 0 { 274 | // success 275 | err = nil 276 | } 277 | 278 | return 279 | } 280 | -------------------------------------------------------------------------------- /etw/advapi32_helper.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "unsafe" 8 | ) 9 | 10 | func GetAccessString(guid string) (s string, err error) { 11 | 12 | g := MustParseGUIDFromString(guid) 13 | bSize := uint32(0) 14 | // retrieves size 15 | EventAccessQuery(g, nil, &bSize) 16 | buffer := make([]byte, bSize) 17 | sd := (*SecurityDescriptor)(unsafe.Pointer(&buffer[0])) 18 | // we get the security descriptor 19 | EventAccessQuery(g, sd, &bSize) 20 | 21 | if s, err = ConvertSecurityDescriptorToStringSecurityDescriptorW( 22 | sd, 23 | SDDL_REVISION_1, 24 | DACL_SECURITY_INFORMATION); err != nil { 25 | return 26 | } 27 | 28 | return 29 | } 30 | 31 | /*func AddProviderAccess(guid, sid string, rights uint32) (err error) { 32 | var psid *SID 33 | 34 | if psid, err = ConvertStringSidToSidW(sid); err != nil { 35 | log.Errorf("Failed to convert string to sid: %s", err) 36 | return 37 | } 38 | 39 | g := MustGUIDFromString(guid) 40 | 41 | return EventAccessControl(g, 42 | uint32(EVENT_SECURITY_ADD_DACL), 43 | psid, 44 | rights, 45 | true, 46 | ) 47 | }*/ 48 | -------------------------------------------------------------------------------- /etw/advapi32_test.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/0xrawsec/toast" 10 | ) 11 | 12 | func TestAccessString(t *testing.T) { 13 | 14 | tt := toast.FromT(t) 15 | 16 | //systemSID := "S-1-5-18" 17 | 18 | for _, p := range EnumerateProviders() { 19 | 20 | _, err := GetAccessString(p.GUID) 21 | 22 | tt.CheckErr(err) 23 | 24 | /*err = AddProviderAccess(p.GUID, systemSID, 0x120fff) 25 | // we might have some access denied sometimes 26 | if err == ERROR_ACCESS_DENIED { 27 | continue 28 | } 29 | 30 | tt.CheckErr(err)*/ 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /etw/autologger.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "bytes" 8 | "context" 9 | "encoding/binary" 10 | "encoding/hex" 11 | "fmt" 12 | "os/exec" 13 | "path/filepath" 14 | "strings" 15 | "time" 16 | ) 17 | 18 | const ( 19 | AutologgerPath = `HKLM\System\CurrentControlSet\Control\WMI\Autologger` 20 | regExe = `C:\Windows\System32\reg.exe` 21 | 22 | regDword = "REG_DWORD" 23 | regQword = "REG_QWORD" 24 | regSz = "REG_SZ" 25 | regBinary = "REG_BINARY" 26 | ) 27 | 28 | func hexStr(i interface{}) string { 29 | return fmt.Sprintf("0x%x", i) 30 | } 31 | 32 | type AutoLogger struct { 33 | Name string 34 | Guid string 35 | LogFileMode uint32 36 | BufferSize uint32 37 | ClockType uint32 38 | // MaxFileSize is the maximum file size of the log file, in megabytes. 39 | // If a real time session with RealtimePersistence this is the maximum file size of the backup file. 40 | // If not set the default is 100MB, to specify no limit this parameter must be 0 in the registry. 41 | // But here 0 means that we want don't want to configure MaxFileSize, if we want to set it, this 42 | // member needs to be explicitely set > 0 43 | MaxFileSize uint32 44 | } 45 | 46 | func (a *AutoLogger) Path() string { 47 | return fmt.Sprintf(`%s\%s`, strings.TrimRight(AutologgerPath, `\`), strings.TrimLeft(a.Name, `\`)) 48 | } 49 | 50 | func (a *AutoLogger) Create() (err error) { 51 | sargs := [][]string{ 52 | // ETWtrace parameters 53 | {a.Path(), "GUID", regSz, a.Guid}, 54 | {a.Path(), "Start", regDword, "0x1"}, 55 | {a.Path(), "LogFileMode", regDword, hexStr(a.LogFileMode)}, 56 | // ETWevent can be up to 64KB so buffer needs to be at least this size 57 | {a.Path(), "BufferSize", regDword, hexStr(a.BufferSize)}, 58 | {a.Path(), "ClockType", regDword, hexStr(a.ClockType)}, 59 | } 60 | 61 | if a.MaxFileSize > 0 { 62 | sargs = append(sargs, []string{a.Path(), "MaxFileSize", regDword, hexStr(a.MaxFileSize)}) 63 | } 64 | 65 | for _, args := range sargs { 66 | if err = regAddValue(args[0], args[1], args[2], args[3]); err != nil { 67 | return 68 | } 69 | } 70 | 71 | return 72 | } 73 | 74 | func binaryFilter(filter []uint16) (f string, err error) { 75 | buf := new(bytes.Buffer) 76 | if err = binary.Write(buf, binary.LittleEndian, filter); err != nil { 77 | return 78 | } 79 | return hex.EncodeToString(buf.Bytes()), nil 80 | } 81 | 82 | func (a *AutoLogger) EnableProvider(p Provider) (err error) { 83 | path := fmt.Sprintf(`%s\%s`, a.Path(), p.GUID) 84 | 85 | sargs := [][]string{} 86 | 87 | // ETWtrace parameters 88 | if p.Name != "" { 89 | sargs = append(sargs, []string{path, "ProviderName", regSz, p.Name}) 90 | } 91 | 92 | sargs = append(sargs, []string{path, "Enabled", regDword, "0x1"}) 93 | sargs = append(sargs, []string{path, "EnableLevel", regDword, hexStr(p.EnableLevel)}) 94 | sargs = append(sargs, []string{path, "MatchAnyKeyword", regQword, hexStr(p.MatchAnyKeyword)}) 95 | 96 | if p.MatchAnyKeyword != 0 { 97 | sargs = append(sargs, []string{path, "MatchAllKeyword", regQword, hexStr(p.MatchAllKeyword)}) 98 | } 99 | 100 | // enable event filtering 101 | if len(p.Filter) > 0 { 102 | var binFilter string 103 | if binFilter, err = binaryFilter(p.Filter); err != nil { 104 | return fmt.Errorf("failed to create binary filter: %w", err) 105 | } 106 | filtersPath := filepath.Join(path, "Filters") 107 | sargs = append(sargs, []string{filtersPath, "Enabled", regDword, "0x1"}) 108 | sargs = append(sargs, []string{filtersPath, "EventIdFilterIn", regDword, "0x1"}) 109 | sargs = append(sargs, []string{filtersPath, "EventIds", regBinary, binFilter}) 110 | } 111 | 112 | // executing commands 113 | for _, args := range sargs { 114 | if err = regAddValue(args[0], args[1], args[2], args[3]); err != nil { 115 | return 116 | } 117 | } 118 | 119 | return 120 | } 121 | 122 | func (a *AutoLogger) Exists() bool { 123 | return execute(regExe, "QUERY", a.Path()) == nil 124 | } 125 | 126 | func (a *AutoLogger) Delete() error { 127 | return execute(regExe, "DELETE", a.Path(), "/f") 128 | } 129 | 130 | func execute(name string, args ...string) error { 131 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 132 | defer cancel() 133 | if out, err := exec.CommandContext(ctx, name, args...).CombinedOutput(); err != nil { 134 | return fmt.Errorf("err:%s out:%s", err, string(out)) 135 | } 136 | return nil 137 | } 138 | 139 | func regAddValue(path, valueName, valueType, value string) error { 140 | return execute(regExe, "ADD", path, "/v", valueName, "/t", valueType, "/d", value, "/f") 141 | } 142 | -------------------------------------------------------------------------------- /etw/autologger_test.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/0xrawsec/toast" 10 | ) 11 | 12 | const ( 13 | kernelFileProvider = "Microsoft-Windows-Kernel-File:0xff" 14 | ) 15 | 16 | func TestAutologger(t *testing.T) { 17 | t.Parallel() 18 | 19 | tt := toast.FromT(t) 20 | 21 | guid, err := UUID() 22 | tt.CheckErr(err) 23 | 24 | a := AutoLogger{ 25 | Name: "AutologgerTest", 26 | Guid: guid, 27 | LogFileMode: 0x8001c0, 28 | BufferSize: 64, 29 | ClockType: 2, 30 | } 31 | 32 | defer a.Delete() 33 | 34 | tt.CheckErr(a.Create()) 35 | provider, err := ParseProvider(kernelFileProvider) 36 | tt.CheckErr(err) 37 | tt.CheckErr(a.EnableProvider(provider)) 38 | tt.Assert(a.Exists()) 39 | } 40 | -------------------------------------------------------------------------------- /etw/consumer.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "reflect" 10 | "sync" 11 | "syscall" 12 | ) 13 | 14 | var ( 15 | rtLostEventGuid = MustParseGUIDFromString("{6A399AE0-4BC6-4DE9-870B-3657F8947E7E}") 16 | ) 17 | 18 | // SessionSlice converts a slice of structures implementing Session 19 | // to a slice of Session. 20 | func SessionSlice(i interface{}) (out []Session) { 21 | v := reflect.ValueOf(i) 22 | 23 | switch v.Kind() { 24 | case reflect.Slice: 25 | out = make([]Session, 0, v.Len()) 26 | for i := 0; i < v.Len(); i++ { 27 | if s, ok := v.Index(i).Interface().(Session); ok { 28 | out = append(out, s) 29 | continue 30 | } 31 | panic("slice item must implement Session interface") 32 | } 33 | default: 34 | panic("interface parameter must be []Session") 35 | } 36 | 37 | return 38 | } 39 | 40 | type Consumer struct { 41 | sync.WaitGroup 42 | ctx context.Context 43 | cancel context.CancelFunc 44 | traceHandles []syscall.Handle 45 | lastError error 46 | closed bool 47 | 48 | // First callback executed, it allows to filter out events 49 | // based on fields of raw ETW EventRecord structure. When this callback 50 | // returns true event processing will continue, otherwise it is aborted. 51 | // Filtering out events here has the lowest overhead. 52 | EventRecordCallback func(*EventRecord) bool 53 | 54 | // Callback which executes after TraceEventInfo is parsed. 55 | // To filter out some events call Skip method of EventRecordHelper 56 | // As Properties are not parsed yet, trying to get/set Properties is 57 | // not possible and might cause unexpected behaviours. 58 | EventRecordHelperCallback func(*EventRecordHelper) error 59 | 60 | // Callback executed after event properties got prepared (step before parsing). 61 | // Properties are not parsed yet and this is the right place to filter 62 | // events based only on some properties. 63 | // NB: events skipped in EventRecordCallback never reach this function 64 | PreparedCallback func(*EventRecordHelper) error 65 | 66 | // Callback executed after the event got parsed and defines what to do 67 | // with the event (printed, sent to a channel ...) 68 | EventCallback func(*Event) error 69 | 70 | Traces map[string]bool 71 | Filter EventFilter 72 | Events chan *Event 73 | 74 | LostEvents uint64 75 | 76 | Skipped uint64 77 | } 78 | 79 | // NewRealTimeConsumer creates a new Consumer to consume ETW 80 | // in RealTime mode 81 | func NewRealTimeConsumer(ctx context.Context) (c *Consumer) { 82 | c = &Consumer{ 83 | traceHandles: make([]syscall.Handle, 0, 64), 84 | Traces: make(map[string]bool), 85 | Filter: NewProviderFilter(), 86 | Events: make(chan *Event, 4096), 87 | } 88 | 89 | c.ctx, c.cancel = context.WithCancel(ctx) 90 | c.EventRecordHelperCallback = c.DefaultEventRecordCallback 91 | c.EventCallback = c.DefaultEventCallback 92 | 93 | return c 94 | } 95 | 96 | func (c *Consumer) bufferCallback(e *EventTraceLogfile) uintptr { 97 | if c.ctx.Err() != nil { 98 | // if the consumer has been stopped we 99 | // don't process event records anymore 100 | return 0 101 | } 102 | // we keep processing event records 103 | return 1 104 | } 105 | 106 | func (c *Consumer) callback(er *EventRecord) (rc uintptr) { 107 | var event *Event 108 | 109 | if er.EventHeader.ProviderId.Equals(rtLostEventGuid) { 110 | c.LostEvents++ 111 | } 112 | 113 | // calling EventHeaderCallback if possible 114 | if c.EventRecordCallback != nil { 115 | if !c.EventRecordCallback(er) { 116 | return 117 | } 118 | } 119 | 120 | // we get the consumer from user context 121 | if h, err := newEventRecordHelper(er); err == nil { 122 | 123 | if c.EventRecordHelperCallback != nil { 124 | if err = c.EventRecordHelperCallback(h); err != nil { 125 | c.lastError = err 126 | } 127 | } 128 | 129 | // if event must be skipped we do not further process it 130 | if h.Flags.Skip { 131 | return 132 | } 133 | 134 | // initialize record helper 135 | h.initialize() 136 | 137 | if err := h.prepareProperties(); err != nil { 138 | c.lastError = err 139 | return 140 | } 141 | 142 | // running a hook before parsing event properties 143 | if c.PreparedCallback != nil { 144 | if err := c.PreparedCallback(h); err != nil { 145 | c.lastError = err 146 | } 147 | } 148 | 149 | // check if we must skip event after next hook 150 | if h.Flags.Skip || c.EventCallback == nil { 151 | return 152 | } 153 | 154 | if event, err = h.buildEvent(); err != nil { 155 | c.lastError = err 156 | } 157 | 158 | if err := c.EventCallback(event); err != nil { 159 | c.lastError = err 160 | } 161 | } 162 | 163 | return 164 | } 165 | 166 | func (c *Consumer) newRealTimeLogfile() (loggerInfo EventTraceLogfile) { 167 | // PROCESS_TRACE_MODE_EVENT_RECORD to receive EventRecords (new format) 168 | // PROCESS_TRACE_MODE_RAW_TIMESTAMP don't convert TimeStamp member of EVENT_HEADER and EVENT_TRACE_HEADER converted to system time 169 | // PROCESS_TRACE_MODE_REAL_TIME to receive events in real time 170 | //loggerInfo.SetProcessTraceMode(PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_RAW_TIMESTAMP | PROCESS_TRACE_MODE_REAL_TIME) 171 | loggerInfo.SetProcessTraceMode(PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_REAL_TIME) 172 | loggerInfo.BufferCallback = syscall.NewCallbackCDecl(c.bufferCallback) 173 | loggerInfo.Callback = syscall.NewCallbackCDecl(c.callback) 174 | return 175 | } 176 | 177 | // close closes the Consumer and eventually waits for ProcessTraces calls 178 | // to end 179 | func (c *Consumer) close(wait bool) (lastErr error) { 180 | if c.closed { 181 | return 182 | } 183 | 184 | // closing trace handles 185 | for _, h := range c.traceHandles { 186 | // if we don't wait for traces ERROR_CTX_CLOSE_PENDING is a valid error 187 | if err := CloseTrace(h); err != nil && err != ERROR_CTX_CLOSE_PENDING { 188 | lastErr = err 189 | } 190 | } 191 | 192 | if wait { 193 | c.Wait() 194 | } 195 | 196 | close(c.Events) 197 | c.closed = true 198 | 199 | return 200 | } 201 | 202 | // OpenTrace opens a 203 | func (c *Consumer) OpenTrace(name string) (err error) { 204 | var traceHandle syscall.Handle 205 | 206 | loggerInfo := c.newRealTimeLogfile() 207 | 208 | // We use the session name to open the trace 209 | if loggerInfo.LoggerName, err = syscall.UTF16PtrFromString(name); err != nil { 210 | return err 211 | } 212 | 213 | if traceHandle, err = OpenTrace(&loggerInfo); err != nil { 214 | return err 215 | } 216 | 217 | c.traceHandles = append(c.traceHandles, syscall.Handle(traceHandle)) 218 | return nil 219 | } 220 | 221 | // FromSessions initializes the consumer from sessions 222 | func (c *Consumer) FromSessions(sessions ...Session) *Consumer { 223 | 224 | for _, s := range sessions { 225 | c.InitFilters(s.Providers()) 226 | c.Traces[s.TraceName()] = true 227 | } 228 | 229 | return c 230 | } 231 | 232 | // FromTraceNames initializes consumer from existing traces 233 | func (c *Consumer) FromTraceNames(names ...string) *Consumer { 234 | for _, n := range names { 235 | c.Traces[n] = true 236 | } 237 | return c 238 | } 239 | 240 | // InitFilters initializes event filtering from a Provider slice 241 | func (c *Consumer) InitFilters(providers []Provider) { 242 | for _, p := range providers { 243 | c.Filter.Update(&p) 244 | } 245 | } 246 | 247 | // DefaultEventRecordCallback is the default EventRecordCallback method applied 248 | // to Consumer created with NewRealTimeConsumer 249 | func (c *Consumer) DefaultEventRecordCallback(h *EventRecordHelper) error { 250 | h.Flags.Skip = !c.Filter.Match(h) 251 | return nil 252 | } 253 | 254 | // DefaultEventCallback is the default EventCallback method applied 255 | // to Consumer created with NewRealTimeConsumer 256 | func (c *Consumer) DefaultEventCallback(event *Event) (err error) { 257 | // we have to check again here as the lock introduced delay 258 | if c.ctx.Err() == nil { 259 | 260 | // if the event can be skipped we send it in a non-blocking way 261 | if event.Flags.Skippable { 262 | select { 263 | case c.Events <- event: 264 | default: 265 | c.Skipped++ 266 | } 267 | 268 | return 269 | } 270 | 271 | // if we cannot skip event we send it in a blocking way 272 | c.Events <- event 273 | } 274 | 275 | return 276 | } 277 | 278 | // Start starts the consumer 279 | func (c *Consumer) Start() (err error) { 280 | 281 | // opening all traces first 282 | for n := range c.Traces { 283 | if err = c.OpenTrace(n); err != nil { 284 | return fmt.Errorf("failed to open trace %s: %w", n, err) 285 | } 286 | } 287 | 288 | for i := range c.traceHandles { 289 | i := i 290 | c.Add(1) 291 | go func() { 292 | defer c.Done() 293 | // ProcessTrace can contain only ONE handle to a real-time processing session 294 | // src: https://docs.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-processtrace 295 | if err := ProcessTrace(&c.traceHandles[i], 1, nil, nil); err != nil { 296 | c.lastError = err 297 | } 298 | }() 299 | } 300 | 301 | return 302 | } 303 | 304 | // Err returns the last error encountered by the consumer 305 | func (c *Consumer) Err() error { 306 | return c.lastError 307 | } 308 | 309 | // Stop stops the Consumer and waits for the ProcessTrace calls 310 | // to be terminated 311 | func (c *Consumer) Stop() (err error) { 312 | // calling context cancel function 313 | c.cancel() 314 | return c.close(true) 315 | } 316 | -------------------------------------------------------------------------------- /etw/etw_helpers.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "fmt" 8 | "math" 9 | "os" 10 | "strconv" 11 | "syscall" 12 | "unsafe" 13 | 14 | "github.com/0xrawsec/golang-utils/log" 15 | ) 16 | 17 | const ( 18 | StructurePropertyName = "Structures" 19 | ) 20 | 21 | var ( 22 | hostname, _ = os.Hostname() 23 | 24 | ErrPropertyParsing = fmt.Errorf("error parsing property") 25 | ErrUnknownProperty = fmt.Errorf("unknown property") 26 | ) 27 | 28 | type Property struct { 29 | evtRecordHelper *EventRecordHelper 30 | evtPropInfo *EventPropertyInfo 31 | 32 | name string 33 | value string 34 | length uint32 35 | 36 | pValue uintptr 37 | userDataLength uint16 38 | } 39 | 40 | func maxu32(a, b uint32) uint32 { 41 | if a < b { 42 | return b 43 | } 44 | return a 45 | } 46 | 47 | func (p *Property) Parseable() bool { 48 | return p.evtRecordHelper != nil && p.evtPropInfo != nil && p.pValue > 0 49 | } 50 | 51 | func (p *Property) Value() (string, error) { 52 | var err error 53 | 54 | if p.value == "" && p.Parseable() { 55 | // we parse only if not already done 56 | p.value, err = p.parse() 57 | } 58 | 59 | return p.value, err 60 | } 61 | 62 | func (p *Property) parse() (value string, err error) { 63 | var mapInfo *EventMapInfo 64 | var udc uint16 65 | var buff []uint16 66 | 67 | formattedDataSize := maxu32(16, p.length) 68 | 69 | // Get the name/value mapping if the property specifies a value map. 70 | if p.evtPropInfo.MapNameOffset() > 0 { 71 | pMapName := (*uint16)(unsafe.Pointer(p.evtRecordHelper.TraceInfo.pointerOffset(uintptr(p.evtPropInfo.MapNameOffset())))) 72 | decSrc := p.evtRecordHelper.TraceInfo.DecodingSource 73 | if mapInfo, err = p.evtRecordHelper.EventRec.GetMapInfo(pMapName, uint32(decSrc)); err != nil { 74 | err = fmt.Errorf("failed to get map info: %s", err) 75 | return 76 | } 77 | } 78 | 79 | for { 80 | buff = make([]uint16, formattedDataSize) 81 | 82 | err = TdhFormatProperty( 83 | p.evtRecordHelper.TraceInfo, 84 | mapInfo, 85 | p.evtRecordHelper.EventRec.PointerSize(), 86 | p.evtPropInfo.InType(), 87 | p.evtPropInfo.OutType(), 88 | uint16(p.length), 89 | p.userDataLength, 90 | (*byte)(unsafe.Pointer(p.pValue)), 91 | &formattedDataSize, 92 | &buff[0], 93 | &udc) 94 | 95 | if err == syscall.ERROR_INSUFFICIENT_BUFFER { 96 | continue 97 | } 98 | 99 | if err == ERROR_EVT_INVALID_EVENT_DATA { 100 | if mapInfo == nil { 101 | break 102 | } 103 | mapInfo = nil 104 | continue 105 | } 106 | 107 | if err == nil { 108 | break 109 | } 110 | 111 | err = fmt.Errorf("failed to format property : %s", err) 112 | return 113 | } 114 | 115 | value = syscall.UTF16ToString(buff) 116 | 117 | return 118 | } 119 | 120 | type EventRecordHelper struct { 121 | EventRec *EventRecord 122 | TraceInfo *TraceEventInfo 123 | 124 | Properties map[string]*Property 125 | ArrayProperties map[string][]*Property 126 | Structures []map[string]*Property 127 | 128 | Flags struct { 129 | Skip bool 130 | Skippable bool 131 | } 132 | 133 | userDataIt uintptr 134 | selectedProperties map[string]bool 135 | } 136 | 137 | func newEventRecordHelper(er *EventRecord) (erh *EventRecordHelper, err error) { 138 | erh = &EventRecordHelper{} 139 | erh.EventRec = er 140 | 141 | if erh.TraceInfo, err = er.GetEventInformation(); err != nil { 142 | return 143 | } 144 | 145 | return 146 | } 147 | 148 | func (e *EventRecordHelper) initialize() { 149 | e.Properties = make(map[string]*Property) 150 | e.ArrayProperties = make(map[string][]*Property) 151 | e.Structures = make([]map[string]*Property, 0) 152 | e.selectedProperties = make(map[string]bool) 153 | 154 | e.userDataIt = e.EventRec.UserData 155 | } 156 | 157 | func (e *EventRecordHelper) setEventMetadata(event *Event) { 158 | event.System.Computer = hostname 159 | event.System.Execution.ProcessID = e.EventRec.EventHeader.ProcessId 160 | event.System.Execution.ThreadID = e.EventRec.EventHeader.ThreadId 161 | event.System.Correlation.ActivityID = e.EventRec.EventHeader.ActivityId.String() 162 | event.System.Correlation.RelatedActivityID = e.EventRec.RelatedActivityID() 163 | event.System.EventID = e.TraceInfo.EventID() 164 | event.System.Channel = e.TraceInfo.ChannelName() 165 | event.System.Provider.Guid = e.TraceInfo.ProviderGUID.String() 166 | event.System.Provider.Name = e.TraceInfo.ProviderName() 167 | event.System.Level.Value = e.TraceInfo.EventDescriptor.Level 168 | event.System.Level.Name = e.TraceInfo.LevelName() 169 | event.System.Opcode.Value = e.TraceInfo.EventDescriptor.Opcode 170 | event.System.Opcode.Name = e.TraceInfo.OpcodeName() 171 | event.System.Keywords.Value = e.TraceInfo.EventDescriptor.Keyword 172 | event.System.Keywords.Name = e.TraceInfo.KeywordName() 173 | event.System.Task.Value = uint8(e.TraceInfo.EventDescriptor.Task) 174 | event.System.Task.Name = e.TraceInfo.TaskName() 175 | event.System.TimeCreated.SystemTime = e.EventRec.EventHeader.UTCTimeStamp() 176 | 177 | if e.TraceInfo.IsMof() { 178 | var eventType string 179 | if t, ok := MofClassMapping[e.TraceInfo.EventGUID.Data1]; ok { 180 | eventType = fmt.Sprintf("%s/%s", t.Name, event.System.Opcode.Name) 181 | } else { 182 | eventType = fmt.Sprintf("UnknownClass/%s", event.System.Opcode.Name) 183 | } 184 | event.System.EventType = eventType 185 | event.System.EventGuid = e.TraceInfo.EventGUID.String() 186 | } 187 | } 188 | 189 | func (e *EventRecordHelper) endUserData() uintptr { 190 | return e.EventRec.UserData + uintptr(e.EventRec.UserDataLength) 191 | } 192 | 193 | func (e *EventRecordHelper) userDataLength() uint16 { 194 | return uint16(e.endUserData() - e.userDataIt) 195 | } 196 | 197 | func (e *EventRecordHelper) getPropertyLength(i uint32) (uint32, error) { 198 | if epi := e.TraceInfo.GetEventPropertyInfoAt(i); epi.Flags&PropertyParamLength == PropertyParamLength { 199 | propSize := uint32(0) 200 | length := uint32(0) 201 | j := uint32(epi.LengthPropertyIndex()) 202 | pdd := PropertyDataDescriptor{} 203 | pdd.PropertyName = uint64(e.TraceInfo.pointer()) + uint64(e.TraceInfo.GetEventPropertyInfoAt(j).NameOffset) 204 | pdd.ArrayIndex = math.MaxUint32 205 | if err := TdhGetPropertySize(e.EventRec, 0, nil, 1, &pdd, &propSize); err != nil { 206 | return 0, fmt.Errorf("failed to get property size: %s", err) 207 | } else { 208 | if err := TdhGetProperty(e.EventRec, 0, nil, 1, &pdd, propSize, (*byte)(unsafe.Pointer(&length))); err != nil { 209 | return 0, fmt.Errorf("failed to get property: %s", err) 210 | } 211 | return length, nil 212 | } 213 | } else { 214 | if epi.Length() > 0 { 215 | return uint32(epi.Length()), nil 216 | } else { 217 | switch { 218 | // if there is an error returned here just try to add a switch case 219 | // with the proper in type 220 | case epi.InType() == uint16(TdhInTypeBinary) && epi.OutType() == uint16(TdhOutTypeIpv6): 221 | // sizeof(IN6_ADDR) == 16 222 | return uint32(16), nil 223 | case epi.InType() == uint16(TdhInTypeUnicodestring): 224 | return uint32(epi.Length()), nil 225 | case epi.InType() == uint16(TdhInTypeAnsistring): 226 | return uint32(epi.Length()), nil 227 | case epi.InType() == uint16(TdhInTypeSid): 228 | return uint32(epi.Length()), nil 229 | case epi.InType() == uint16(TdhInTypeWbemsid): 230 | return uint32(epi.Length()), nil 231 | case epi.Flags&PropertyStruct == PropertyStruct: 232 | return uint32(epi.Length()), nil 233 | default: 234 | return 0, fmt.Errorf("unexpected length of 0 for intype %d and outtype %d", epi.InType(), epi.OutType()) 235 | } 236 | } 237 | } 238 | } 239 | 240 | func (e *EventRecordHelper) getPropertySize(i uint32) (size uint32, err error) { 241 | dataDesc := PropertyDataDescriptor{} 242 | dataDesc.PropertyName = uint64(e.TraceInfo.PropertyNameOffset(i)) 243 | dataDesc.ArrayIndex = math.MaxUint32 244 | err = TdhGetPropertySize(e.EventRec, 0, nil, 1, &dataDesc, &size) 245 | return 246 | } 247 | 248 | func (e *EventRecordHelper) getArraySize(i uint32) (arraySize uint16, err error) { 249 | dataDesc := PropertyDataDescriptor{} 250 | propSz := uint32(0) 251 | 252 | epi := e.TraceInfo.GetEventPropertyInfoAt(i) 253 | if (epi.Flags & PropertyParamCount) == PropertyParamCount { 254 | count := uint32(0) 255 | j := epi.CountUnion 256 | dataDesc.PropertyName = uint64(e.TraceInfo.pointer() + uintptr(e.TraceInfo.GetEventPropertyInfoAt(uint32(j)).NameOffset)) 257 | dataDesc.ArrayIndex = math.MaxUint32 258 | if err = TdhGetPropertySize(e.EventRec, 0, nil, 1, &dataDesc, &propSz); err != nil { 259 | return 260 | } 261 | if err = TdhGetProperty(e.EventRec, 0, nil, 1, &dataDesc, propSz, ((*byte)(unsafe.Pointer(&count)))); err != nil { 262 | return 263 | } 264 | arraySize = uint16(count) 265 | } else { 266 | arraySize = epi.CountUnion 267 | } 268 | return 269 | } 270 | 271 | func (e *EventRecordHelper) prepareProperty(i uint32) (p *Property, err error) { 272 | var size uint32 273 | 274 | p = &Property{} 275 | 276 | p.evtPropInfo = e.TraceInfo.GetEventPropertyInfoAt(i) 277 | p.evtRecordHelper = e 278 | p.name = UTF16AtOffsetToString(e.TraceInfo.pointer(), uintptr(p.evtPropInfo.NameOffset)) 279 | p.pValue = e.userDataIt 280 | p.userDataLength = e.userDataLength() 281 | 282 | if p.length, err = e.getPropertyLength(i); err != nil { 283 | err = fmt.Errorf("failed to get property length: %s", err) 284 | return 285 | } 286 | 287 | // size is different from length 288 | if size, err = e.getPropertySize(i); err != nil { 289 | return 290 | } 291 | 292 | e.userDataIt += uintptr(size) 293 | 294 | return 295 | } 296 | 297 | func (e *EventRecordHelper) prepareProperties() (last error) { 298 | var arraySize uint16 299 | var p *Property 300 | 301 | for i := uint32(0); i < e.TraceInfo.TopLevelPropertyCount; i++ { 302 | epi := e.TraceInfo.GetEventPropertyInfoAt(i) 303 | isArray := epi.Flags&PropertyParamCount == PropertyParamCount 304 | 305 | switch { 306 | case isArray: 307 | log.Debugf("Property is an array") 308 | case epi.Flags&PropertyParamLength == PropertyParamLength: 309 | log.Debugf("Property is a buffer") 310 | case epi.Flags&PropertyParamCount == PropertyStruct: 311 | log.Debugf("Property is a struct") 312 | default: 313 | // property is a map 314 | } 315 | 316 | if arraySize, last = e.getArraySize(i); last != nil { 317 | return 318 | } else { 319 | var arrayName string 320 | var array []*Property 321 | 322 | // this is not because we have arraySize > 0 that we are an array 323 | // so if we deal with an array property 324 | if isArray { 325 | array = make([]*Property, 0) 326 | } 327 | 328 | for k := uint16(0); k < arraySize; k++ { 329 | 330 | // If the property is a structure 331 | if epi.Flags&PropertyStruct == PropertyStruct { 332 | log.Debugf("structure over here") 333 | propStruct := make(map[string]*Property) 334 | lastMember := epi.StructStartIndex() + epi.NumOfStructMembers() 335 | 336 | for j := epi.StructStartIndex(); j < lastMember; j++ { 337 | log.Debugf("parsing struct property: %d", j) 338 | if p, last = e.prepareProperty(uint32(j)); last != nil { 339 | return 340 | } else { 341 | propStruct[p.name] = p 342 | } 343 | } 344 | 345 | e.Structures = append(e.Structures, propStruct) 346 | 347 | continue 348 | } 349 | 350 | if p, last = e.prepareProperty(i); last != nil { 351 | return 352 | } 353 | 354 | if isArray { 355 | arrayName = p.name 356 | array = append(array, p) 357 | continue 358 | } 359 | 360 | e.Properties[p.name] = p 361 | } 362 | 363 | if len(array) > 0 { 364 | e.ArrayProperties[arrayName] = array 365 | } 366 | } 367 | } 368 | 369 | return 370 | } 371 | 372 | func (e *EventRecordHelper) buildEvent() (event *Event, err error) { 373 | event = NewEvent() 374 | 375 | event.Flags.Skippable = e.Flags.Skippable 376 | 377 | if err = e.parseAndSetAllProperties(event); err != nil { 378 | return 379 | } 380 | 381 | e.setEventMetadata(event) 382 | 383 | return 384 | } 385 | 386 | func (e *EventRecordHelper) parseAndSetProperty(name string, out *Event) (err error) { 387 | 388 | eventData := out.EventData 389 | 390 | // it is a user data property 391 | if (e.TraceInfo.Flags & TEMPLATE_USER_DATA) == TEMPLATE_USER_DATA { 392 | eventData = out.UserData 393 | } 394 | 395 | if p, ok := e.Properties[name]; ok { 396 | if eventData[p.name], err = p.Value(); err != nil { 397 | return fmt.Errorf("%w %s: %s", ErrPropertyParsing, name, err) 398 | } 399 | } 400 | 401 | // parsing array 402 | if props, ok := e.ArrayProperties[name]; ok { 403 | values := make([]string, len(props)) 404 | 405 | // iterate over the properties 406 | for _, p := range props { 407 | var v string 408 | if v, err = p.Value(); err != nil { 409 | return fmt.Errorf("%w array %s: %s", ErrPropertyParsing, name, err) 410 | } 411 | 412 | values = append(values, v) 413 | } 414 | 415 | eventData[name] = values 416 | } 417 | 418 | // parsing structures 419 | if name == StructurePropertyName { 420 | if len(e.Structures) > 0 { 421 | structs := make([]map[string]string, len(e.Structures)) 422 | for _, m := range e.Structures { 423 | s := make(map[string]string) 424 | for field, prop := range m { 425 | if s[field], err = prop.Value(); err != nil { 426 | return fmt.Errorf("%w %s.%s: %s", ErrPropertyParsing, StructurePropertyName, field, err) 427 | } 428 | } 429 | } 430 | 431 | eventData[StructurePropertyName] = structs 432 | } 433 | } 434 | 435 | return 436 | } 437 | 438 | func (e *EventRecordHelper) shouldParse(name string) bool { 439 | if len(e.selectedProperties) == 0 { 440 | return true 441 | } 442 | _, ok := e.selectedProperties[name] 443 | return ok 444 | } 445 | 446 | func (e *EventRecordHelper) parseAndSetAllProperties(out *Event) (last error) { 447 | var err error 448 | 449 | eventData := out.EventData 450 | 451 | // it is a user data property 452 | if (e.TraceInfo.Flags & TEMPLATE_USER_DATA) == TEMPLATE_USER_DATA { 453 | eventData = out.UserData 454 | } 455 | 456 | // Properties 457 | for pname, p := range e.Properties { 458 | if !e.shouldParse(pname) { 459 | continue 460 | } 461 | /*if err := e.parseAndSetProperty(pname, out); err != nil { 462 | last = err 463 | }*/ 464 | if eventData[p.name], err = p.Value(); err != nil { 465 | last = fmt.Errorf("%w %s: %s", ErrPropertyParsing, p.name, err) 466 | } 467 | } 468 | 469 | // Arrays 470 | for pname, props := range e.ArrayProperties { 471 | if !e.shouldParse(pname) { 472 | continue 473 | } 474 | 475 | values := make([]string, len(props)) 476 | 477 | // iterate over the properties 478 | for _, p := range props { 479 | var v string 480 | if v, err = p.Value(); err != nil { 481 | last = fmt.Errorf("%w array %s: %s", ErrPropertyParsing, pname, err) 482 | } 483 | 484 | values = append(values, v) 485 | } 486 | 487 | eventData[pname] = values 488 | } 489 | 490 | // Structure 491 | if !e.shouldParse(StructurePropertyName) { 492 | return 493 | } 494 | 495 | if len(e.Structures) > 0 { 496 | structs := make([]map[string]string, len(e.Structures)) 497 | for _, m := range e.Structures { 498 | s := make(map[string]string) 499 | for field, prop := range m { 500 | if s[field], err = prop.Value(); err != nil { 501 | last = fmt.Errorf("%w %s.%s: %s", ErrPropertyParsing, StructurePropertyName, field, err) 502 | } 503 | } 504 | } 505 | 506 | eventData[StructurePropertyName] = structs 507 | } 508 | 509 | return 510 | } 511 | 512 | /** Public methods **/ 513 | 514 | // SelectFields selects the properties that will be parsed and populated 515 | // in the parsed ETW event. If this method is not called, all properties will 516 | // be parsed and put in the event. 517 | func (e *EventRecordHelper) SelectFields(names ...string) { 518 | for _, n := range names { 519 | e.selectedProperties[n] = true 520 | } 521 | } 522 | 523 | func (e *EventRecordHelper) ProviderGUID() string { 524 | return e.TraceInfo.ProviderGUID.String() 525 | } 526 | 527 | func (e *EventRecordHelper) Provider() string { 528 | return e.TraceInfo.ProviderName() 529 | } 530 | 531 | func (e *EventRecordHelper) Channel() string { 532 | return e.TraceInfo.ChannelName() 533 | } 534 | 535 | func (e *EventRecordHelper) EventID() uint16 { 536 | return e.TraceInfo.EventID() 537 | } 538 | 539 | func (e *EventRecordHelper) GetPropertyString(name string) (s string, err error) { 540 | 541 | if p, ok := e.Properties[name]; ok { 542 | return p.Value() 543 | } 544 | 545 | return "", fmt.Errorf("%w %s", ErrUnknownProperty, name) 546 | } 547 | 548 | func (e *EventRecordHelper) GetPropertyInt(name string) (i int64, err error) { 549 | var s string 550 | 551 | if s, err = e.GetPropertyString(name); err != nil { 552 | return 553 | } 554 | 555 | return strconv.ParseInt(s, 0, 64) 556 | } 557 | 558 | func (e *EventRecordHelper) GetPropertyUint(name string) (u uint64, err error) { 559 | var s string 560 | 561 | if s, err = e.GetPropertyString(name); err != nil { 562 | return 563 | } 564 | 565 | return strconv.ParseUint(s, 0, 64) 566 | } 567 | 568 | func (e *EventRecordHelper) SetProperty(name, value string) { 569 | 570 | if p, ok := e.Properties[name]; ok { 571 | p.value = value 572 | return 573 | } 574 | 575 | e.Properties[name] = &Property{name: name, value: value} 576 | } 577 | 578 | func (e *EventRecordHelper) ParseProperties(names ...string) (err error) { 579 | for _, name := range names { 580 | if err = e.ParseProperty(name); err != nil { 581 | return 582 | } 583 | } 584 | 585 | return 586 | } 587 | 588 | func (e *EventRecordHelper) ParseProperty(name string) (err error) { 589 | if p, ok := e.Properties[name]; ok { 590 | if _, err = p.Value(); err != nil { 591 | return fmt.Errorf("%w %s: %s", ErrPropertyParsing, name, err) 592 | } 593 | } 594 | 595 | // parsing array 596 | if props, ok := e.ArrayProperties[name]; ok { 597 | // iterate over the properties 598 | for _, p := range props { 599 | if _, err = p.Value(); err != nil { 600 | return fmt.Errorf("%w array %s: %s", ErrPropertyParsing, name, err) 601 | } 602 | } 603 | } 604 | 605 | // parsing structures 606 | if name == StructurePropertyName { 607 | if len(e.Structures) > 0 { 608 | for _, m := range e.Structures { 609 | s := make(map[string]string) 610 | for field, prop := range m { 611 | if s[field], err = prop.Value(); err != nil { 612 | return fmt.Errorf("%w %s.%s: %s", ErrPropertyParsing, StructurePropertyName, field, err) 613 | } 614 | } 615 | } 616 | } 617 | } 618 | 619 | return 620 | } 621 | 622 | func (e *EventRecordHelper) Skippable() { 623 | e.Flags.Skippable = true 624 | } 625 | 626 | func (e *EventRecordHelper) Skip() { 627 | e.Flags.Skip = true 628 | } 629 | -------------------------------------------------------------------------------- /etw/etw_test.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "context" 8 | "encoding/json" 9 | "fmt" 10 | "math/rand" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | "sync" 15 | "testing" 16 | "time" 17 | 18 | "github.com/0xrawsec/toast" 19 | ) 20 | 21 | const ( 22 | // providers 23 | SysmonProvider = "{5770385F-C22A-43E0-BF4C-06F5698FFBD9}" 24 | KernelMemoryProviderName = "{D1D93EF7-E1F2-4F45-9943-03D245FE6C00}" 25 | KernelFileProviderName = "Microsoft-Windows-Kernel-File" 26 | // sessions 27 | EventlogSecurity = "Eventlog-Security" 28 | ) 29 | 30 | func init() { 31 | rand.Seed(time.Now().UnixNano()) 32 | } 33 | 34 | func randBetween(min, max int) (i int) { 35 | for ; i < min; i = rand.Int() % max { 36 | } 37 | return 38 | } 39 | 40 | func TestIsKnownProvider(t *testing.T) { 41 | t.Parallel() 42 | 43 | tt := toast.FromT(t) 44 | 45 | tt.Assert(IsKnownProvider("Microsoft-Windows-Kernel-File")) 46 | tt.Assert(!IsKnownProvider("Microsoft-Windows-Unknown-Provider")) 47 | } 48 | 49 | func TestProducerConsumer(t *testing.T) { 50 | var prov Provider 51 | var err error 52 | 53 | eventCount := 0 54 | tt := toast.FromT(t) 55 | 56 | // Producer part 57 | prod := NewRealTimeSession("GolangTest") 58 | 59 | prov, err = ParseProvider(KernelFileProviderName + ":0xff:12,13,14,15,16") 60 | tt.CheckErr(err) 61 | // enabling provider 62 | tt.CheckErr(prod.EnableProvider(prov)) 63 | // starting producer 64 | tt.CheckErr(prod.Start()) 65 | // checking producer is running 66 | tt.Assert(prod.IsStarted()) 67 | 68 | defer prod.Stop() 69 | 70 | // Consumer part 71 | c := NewRealTimeConsumer(context.Background()).FromSessions(prod).FromTraceNames(EventlogSecurity) 72 | 73 | // we have to declare a func otherwise c.Stop seems to be called 74 | defer func() { tt.CheckErr(c.Stop()) }() 75 | // starting consumer 76 | tt.CheckErr(c.Start()) 77 | 78 | start := time.Now() 79 | // consuming events in Golang 80 | go func() { 81 | for e := range c.Events { 82 | eventCount++ 83 | 84 | if e.System.Provider.Name == KernelFileProviderName { 85 | tt.Assert(e.System.EventID == 12 || 86 | e.System.EventID == 13 || 87 | e.System.EventID == 14 || 88 | e.System.EventID == 15 || 89 | e.System.EventID == 16) 90 | } 91 | 92 | _, err := json.Marshal(&e) 93 | tt.CheckErr(err) 94 | //t.Log(string(b)) 95 | } 96 | }() 97 | // sleeping 98 | time.Sleep(5 * time.Second) 99 | 100 | // stopping consumer 101 | tt.CheckErr(c.Stop()) 102 | delta := time.Now().Sub(start) 103 | eps := float64(eventCount) / delta.Seconds() 104 | t.Logf("Received: %d events in %s (%d EPS)", eventCount, delta, int(eps)) 105 | 106 | // checking any consumer error 107 | tt.CheckErr(c.Err()) 108 | } 109 | 110 | func TestKernelSession(t *testing.T) { 111 | tt := toast.FromT(t) 112 | eventCount := 0 113 | 114 | traceFlags := []uint32{ 115 | // Trace process creation / termination 116 | //EVENT_TRACE_FLAG_PROCESS, 117 | // Trace image loading 118 | EVENT_TRACE_FLAG_IMAGE_LOAD, 119 | // Trace file operations 120 | //EVENT_TRACE_FLAG_FILE_IO_INIT, 121 | //EVENT_TRACE_FLAG_ALPC, 122 | EVENT_TRACE_FLAG_REGISTRY, 123 | } 124 | 125 | // producer part 126 | kp := NewKernelRealTimeSession(traceFlags...) 127 | 128 | // starting kernel producer 129 | tt.CheckErr(kp.Start()) 130 | // checking producer is started 131 | tt.Assert(kp.IsStarted()) 132 | 133 | // consumer part 134 | c := NewRealTimeConsumer(context.Background()).FromSessions(kp) 135 | 136 | // we have to declare a func otherwise c.Stop seems to be called 137 | defer func() { tt.CheckErr(c.Stop()) }() 138 | 139 | tt.CheckErr(c.Start()) 140 | 141 | start := time.Now() 142 | wg := sync.WaitGroup{} 143 | wg.Add(1) 144 | go func() { 145 | defer wg.Done() 146 | for e := range c.Events { 147 | eventCount++ 148 | 149 | _, err := json.Marshal(&e) 150 | tt.CheckErr(err) 151 | //t.Log(string(b)) 152 | } 153 | }() 154 | 155 | time.Sleep(5 * time.Second) 156 | 157 | tt.CheckErr(c.Stop()) 158 | tt.CheckErr(kp.Stop()) 159 | wg.Wait() 160 | 161 | delta := time.Since(start) 162 | eps := float64(eventCount) / delta.Seconds() 163 | t.Logf("Received: %d events in %s (%d EPS)", eventCount, delta, int(eps)) 164 | } 165 | 166 | func TestEventMapInfo(t *testing.T) { 167 | tt := toast.FromT(t) 168 | eventCount := 0 169 | 170 | prod := NewRealTimeSession("GolangTest") 171 | 172 | mapInfoChannels := []string{ 173 | "Microsoft-Windows-ProcessStateManager", 174 | "Microsoft-Windows-DNS-Client", 175 | "Microsoft-Windows-Win32k", 176 | "Microsoft-Windows-RPC", 177 | "Microsoft-Windows-Kernel-IoTrace"} 178 | 179 | for _, c := range mapInfoChannels { 180 | t.Log(c) 181 | prov, err := ParseProvider(c) 182 | tt.CheckErr(err) 183 | tt.CheckErr(prod.EnableProvider(prov)) 184 | } 185 | 186 | // starting producer 187 | tt.CheckErr(prod.Start()) 188 | // checking producer is running 189 | tt.Assert(prod.IsStarted()) 190 | 191 | defer prod.Stop() 192 | 193 | // consumer part 194 | fakeError := fmt.Errorf("fake") 195 | 196 | c := NewRealTimeConsumer(context.Background()).FromSessions(prod) 197 | // reducing size of channel so that we are obliged to skip events 198 | c.Events = make(chan *Event) 199 | c.PreparedCallback = func(erh *EventRecordHelper) error { 200 | 201 | erh.TraceInfo.EventMessage() 202 | erh.TraceInfo.ActivityIDName() 203 | erh.TraceInfo.RelatedActivityIDName() 204 | 205 | erh.Skip() 206 | 207 | for _, p := range erh.Properties { 208 | // calling those two method just to test they don't cause memory corruption 209 | p.evtPropInfo.Count() 210 | p.evtPropInfo.CountPropertyIndex() 211 | if p.evtPropInfo.MapNameOffset() > 0 { 212 | erh.Flags.Skip = false 213 | } 214 | } 215 | 216 | // don't skip events with related activity ID 217 | erh.Flags.Skip = erh.EventRec.RelatedActivityID() == nullGUIDStr 218 | 219 | return fakeError 220 | } 221 | 222 | // we have to declare a func otherwise c.Stop seems to be called 223 | defer func() { tt.CheckErr(c.Stop()) }() 224 | 225 | tt.CheckErr(c.Start()) 226 | 227 | start := time.Now() 228 | wg := sync.WaitGroup{} 229 | wg.Add(1) 230 | go func() { 231 | defer wg.Done() 232 | for e := range c.Events { 233 | eventCount++ 234 | 235 | _, err := json.Marshal(&e) 236 | tt.CheckErr(err) 237 | if e.System.Correlation.ActivityID != nullGUIDStr && e.System.Correlation.RelatedActivityID != nullGUIDStr { 238 | t.Logf("Provider=%s ActivityID=%s RelatedActivityID=%s", e.System.Provider.Name, e.System.Correlation.ActivityID, e.System.Correlation.RelatedActivityID) 239 | } 240 | //t.Log(string(b)) 241 | } 242 | }() 243 | 244 | time.Sleep(10 * time.Second) 245 | 246 | tt.CheckErr(c.Stop()) 247 | wg.Wait() 248 | 249 | // we got many events so some must have been skipped 250 | t.Logf("skipped %d events", c.Skipped) 251 | tt.Assert(c.Skipped == 0) 252 | 253 | delta := time.Since(start) 254 | eps := float64(eventCount) / delta.Seconds() 255 | t.Logf("Received: %d events in %s (%d EPS)", eventCount, delta, int(eps)) 256 | 257 | tt.ExpectErr(c.Err(), fakeError) 258 | } 259 | 260 | func TestLostEvents(t *testing.T) { 261 | 262 | tt := toast.FromT(t) 263 | 264 | // Producer part 265 | prod := NewRealTimeSession("GolangTest") 266 | // small buffer size on purpose to trigger event loss 267 | prod.properties.BufferSize = 1 268 | 269 | prov, err := ParseProvider("Microsoft-Windows-Kernel-Memory" + ":0xff") 270 | tt.CheckErr(err) 271 | // enabling provider 272 | tt.CheckErr(prod.EnableProvider(prov)) 273 | defer prod.Stop() 274 | 275 | // Consumer part 276 | c := NewRealTimeConsumer(context.Background()).FromSessions(prod).FromTraceNames(EventlogSecurity) 277 | // we have to declare a func otherwise c.Stop does not seem to be called 278 | defer func() { tt.CheckErr(c.Stop()) }() 279 | 280 | // starting consumer 281 | tt.CheckErr(c.Start()) 282 | cnt := uint64(0) 283 | go func() { 284 | for range c.Events { 285 | cnt++ 286 | } 287 | }() 288 | time.Sleep(20 * time.Second) 289 | tt.CheckErr(c.Stop()) 290 | time.Sleep(5 * time.Second) 291 | t.Logf("Events received: %d", cnt) 292 | t.Logf("Events lost: %d", c.LostEvents) 293 | tt.Assert(c.LostEvents > 0) 294 | } 295 | 296 | func jsonStr(i interface{}) string { 297 | var b []byte 298 | var err error 299 | if b, err = json.Marshal(i); err != nil { 300 | panic(err) 301 | } 302 | return string(b) 303 | } 304 | 305 | func TestConsumerCallbacks(t *testing.T) { 306 | var prov Provider 307 | var err error 308 | 309 | eventCount := 0 310 | tt := toast.FromT(t) 311 | 312 | // Producer part 313 | prod := NewRealTimeSession("GolangTest") 314 | 315 | prov, err = ParseProvider(KernelFileProviderName + ":0xff:12,13,14,15,16") 316 | tt.CheckErr(err) 317 | // enabling provider 318 | tt.CheckErr(prod.EnableProvider(prov)) 319 | // starting producer 320 | tt.CheckErr(prod.Start()) 321 | // checking producer is running 322 | tt.Assert(prod.IsStarted()) 323 | kernelFileProviderChannel := prov.Name + "/Analytic" 324 | kernelProviderGUID := MustParseGUIDFromString(prov.GUID) 325 | 326 | defer prod.Stop() 327 | 328 | // Consumer part 329 | c := NewRealTimeConsumer(context.Background()).FromSessions(prod).FromTraceNames(EventlogSecurity) 330 | 331 | c.EventRecordHelperCallback = func(erh *EventRecordHelper) (err error) { 332 | 333 | switch erh.EventID() { 334 | case 12, 14, 15, 16: 335 | break 336 | default: 337 | erh.Skip() 338 | } 339 | 340 | return 341 | } 342 | 343 | type file struct { 344 | name string 345 | flags struct { 346 | read bool 347 | write bool 348 | } 349 | } 350 | 351 | fileObjectMapping := make(map[string]*file) 352 | c.PreparedCallback = func(h *EventRecordHelper) error { 353 | tt.Assert(h.Provider() == prov.Name) 354 | tt.Assert(h.ProviderGUID() == prov.GUID) 355 | tt.Assert(h.EventRec.EventHeader.ProviderId.Equals(kernelProviderGUID)) 356 | tt.Assert(h.TraceInfo.ProviderGUID.Equals(kernelProviderGUID)) 357 | tt.Assert(h.Channel() == kernelFileProviderChannel) 358 | 359 | switch h.EventID() { 360 | case 12: 361 | tt.CheckErr(h.ParseProperties("FileName", "FileObject", "CreateOptions")) 362 | 363 | if fo, err := h.GetPropertyString("FileObject"); err == nil { 364 | if fn, err := h.GetPropertyString("FileName"); err == nil { 365 | fileObjectMapping[fo] = &file{name: fn} 366 | } 367 | } 368 | 369 | coUint, err := h.GetPropertyUint("CreateOptions") 370 | tt.CheckErr(err) 371 | coInt, err := h.GetPropertyInt("CreateOptions") 372 | tt.CheckErr(err) 373 | tt.Assert(coUint != 0 && coUint == uint64(coInt)) 374 | 375 | unk, err := h.GetPropertyString("UnknownProperty") 376 | tt.Assert(unk == "") 377 | tt.ExpectErr(err, ErrUnknownProperty) 378 | 379 | // we skip file create events 380 | h.Skip() 381 | 382 | case 14: 383 | tt.CheckErr(h.ParseProperties("FileObject")) 384 | 385 | if object, err := h.GetPropertyString("FileObject"); err == nil { 386 | delete(fileObjectMapping, object) 387 | } 388 | 389 | // skip file close events 390 | h.Skip() 391 | 392 | case 15, 16: 393 | var f *file 394 | var object string 395 | var ok bool 396 | 397 | tt.CheckErr(h.ParseProperty("FileObject")) 398 | 399 | if object, err = h.GetPropertyString("FileObject"); err != nil { 400 | h.Skip() 401 | break 402 | } 403 | 404 | foUint, _ := h.GetPropertyUint("FileObject") 405 | tt.Assert(fmt.Sprintf("0x%X", foUint) == object) 406 | 407 | if f, ok = fileObjectMapping[object]; !ok { 408 | // we skip events we cannot enrich 409 | h.Skip() 410 | break 411 | } 412 | 413 | if (h.EventID() == 15 && f.flags.read) || 414 | (h.EventID() == 16 && f.flags.write) { 415 | h.Skip() 416 | break 417 | } 418 | 419 | h.SetProperty("FileName", f.name) 420 | f.flags.read = (h.EventID() == 15) 421 | f.flags.write = (h.EventID() == 16) 422 | 423 | // event volume will so low that this call should have no effect 424 | h.Skippable() 425 | 426 | default: 427 | h.Skip() 428 | } 429 | 430 | return nil 431 | } 432 | 433 | // we have to declare a func otherwise c.Stop does not seem to be called 434 | defer func() { tt.CheckErr(c.Stop()) }() 435 | 436 | // starting consumer 437 | tt.CheckErr(c.Start()) 438 | 439 | //testfile := `\Windows\Temp\test.txt` 440 | testfile := filepath.Join(t.TempDir()[2:], "test.txt") 441 | t.Logf("testfile: %s", testfile) 442 | 443 | start := time.Now() 444 | var etwread int 445 | var etwwrite int 446 | 447 | pid := os.Getpid() 448 | // consuming events in Golang 449 | go func() { 450 | for e := range c.Events { 451 | eventCount++ 452 | 453 | _, err := json.Marshal(&e) 454 | tt.CheckErr(err) 455 | switch e.System.EventID { 456 | case 15, 16: 457 | var fn string 458 | var ok bool 459 | 460 | if fn, ok = e.GetPropertyString("FileName"); !ok { 461 | break 462 | } 463 | 464 | if !strings.Contains(fn, testfile) { 465 | break 466 | } 467 | 468 | if e.System.Execution.ProcessID != uint32(pid) { 469 | break 470 | } 471 | 472 | if e.System.EventID == 15 { 473 | etwread++ 474 | } else { 475 | etwwrite++ 476 | } 477 | } 478 | } 479 | }() 480 | 481 | // creating test files 482 | nReadWrite := 0 483 | tf := fmt.Sprintf("C:%s", testfile) 484 | for ; nReadWrite < randBetween(800, 1000); nReadWrite++ { 485 | tmp := fmt.Sprintf("%s.%d", tf, nReadWrite) 486 | tt.CheckErr(os.WriteFile(tmp, []byte("testdata"), 7777)) 487 | _, err = os.ReadFile(tmp) 488 | tt.CheckErr(err) 489 | time.Sleep(time.Millisecond) 490 | } 491 | 492 | d := time.Duration(0) 493 | sleep := time.Second 494 | for d < 10*time.Second { 495 | if etwread == nReadWrite && etwwrite == nReadWrite { 496 | break 497 | } 498 | time.Sleep(sleep) 499 | d += sleep 500 | } 501 | 502 | // wait a couple of seconds more to see if we get more events 503 | time.Sleep(10 * time.Second) 504 | 505 | // stopping consumer 506 | tt.CheckErr(c.Stop()) 507 | 508 | tt.Assert(eventCount != 0, "did not receive any event") 509 | tt.Assert(c.Skipped == 0) 510 | // verifying that we caught all events 511 | t.Logf("read=%d etwread=%d", nReadWrite, etwread) 512 | tt.Assert(nReadWrite == etwread) 513 | t.Logf("write=%d etwwrite=%d", nReadWrite, etwwrite) 514 | tt.Assert(nReadWrite == etwwrite) 515 | 516 | delta := time.Since(start) 517 | eps := float64(eventCount) / delta.Seconds() 518 | t.Logf("Received: %d events in %s (%d EPS)", eventCount, delta, int(eps)) 519 | 520 | // checking any consumer error 521 | tt.CheckErr(c.Err()) 522 | } 523 | 524 | func TestParseProvider(t *testing.T) { 525 | t.Parallel() 526 | 527 | tt := toast.FromT(t) 528 | 529 | if _, err := ParseProvider(KernelFileProviderName); err != nil { 530 | t.Error(err) 531 | } 532 | 533 | p, err := ParseProvider(KernelFileProviderName + ":255") 534 | tt.CheckErr(err) 535 | tt.Assert(p.EnableLevel == 255) 536 | 537 | p, err = ParseProvider(KernelFileProviderName + ":255:0,1,2,3,4:4242") 538 | tt.CheckErr(err) 539 | for i, eventID := range p.Filter { 540 | tt.Assert(i == int(eventID)) 541 | } 542 | 543 | p, err = ParseProvider(KernelFileProviderName + ":255:1,2,3,4:4242") 544 | tt.CheckErr(err) 545 | tt.Assert(p.EnableLevel == 255 && p.MatchAnyKeyword == 4242) 546 | 547 | p, err = ParseProvider(KernelFileProviderName + ":255:1,2,3,4:4242:1337") 548 | tt.CheckErr(err) 549 | tt.Assert(p.EnableLevel == 255 && p.MatchAnyKeyword == 4242 && p.MatchAllKeyword == 1337) 550 | 551 | // this calls must panic on error 552 | MustParseProvider(KernelFileProviderName) 553 | tt.ShouldPanic(func() { MustParseProvider("Microsoft-Unknown-Provider") }) 554 | } 555 | 556 | func TestConvertSid(t *testing.T) { 557 | t.Parallel() 558 | 559 | var sid *SID 560 | var err error 561 | 562 | tt := toast.FromT(t) 563 | systemSID := "S-1-5-18" 564 | 565 | sid, err = ConvertStringSidToSidW(systemSID) 566 | tt.CheckErr(err) 567 | tt.Log(sid) 568 | } 569 | 570 | func TestSessionSlice(t *testing.T) { 571 | t.Parallel() 572 | 573 | tt := toast.FromT(t) 574 | 575 | intSlice := make([]int, 0) 576 | sessions := make([]Session, 0) 577 | for i := 0; i < 10; i++ { 578 | sessions = append(sessions, NewRealTimeSession(fmt.Sprintf("test-%d", i))) 579 | intSlice = append(intSlice, i) 580 | } 581 | 582 | tt.Assert(len(SessionSlice(sessions)) == len(sessions)) 583 | // should panic because parameter is not a slice 584 | tt.ShouldPanic(func() { SessionSlice(sessions[0]) }) 585 | // should panic because items do not implement Session 586 | tt.ShouldPanic(func() { SessionSlice(intSlice) }) 587 | } 588 | -------------------------------------------------------------------------------- /etw/event.go: -------------------------------------------------------------------------------- 1 | package etw 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type EventID uint16 8 | 9 | type Event struct { 10 | Flags struct { 11 | // Use to flag event as being skippable for performance reason 12 | Skippable bool 13 | } `json:"-"` 14 | 15 | EventData map[string]interface{} `json:",omitempty"` 16 | UserData map[string]interface{} `json:",omitempty"` 17 | System struct { 18 | Channel string 19 | Computer string 20 | EventID uint16 21 | EventType string `json:",omitempty"` 22 | EventGuid string `json:",omitempty"` 23 | Correlation struct { 24 | ActivityID string 25 | RelatedActivityID string 26 | } 27 | Execution struct { 28 | ProcessID uint32 29 | ThreadID uint32 30 | } 31 | Keywords struct { 32 | Value uint64 33 | Name string 34 | } 35 | Level struct { 36 | Value uint8 37 | Name string 38 | } 39 | Opcode struct { 40 | Value uint8 41 | Name string 42 | } 43 | Task struct { 44 | Value uint8 45 | Name string 46 | } 47 | Provider struct { 48 | Guid string 49 | Name string 50 | } 51 | TimeCreated struct { 52 | SystemTime time.Time 53 | } 54 | } 55 | ExtendedData []string `json:",omitempty"` 56 | } 57 | 58 | func NewEvent() (e *Event) { 59 | e = &Event{} 60 | e.EventData = make(map[string]interface{}) 61 | e.UserData = make(map[string]interface{}) 62 | e.ExtendedData = make([]string, 0) 63 | return e 64 | } 65 | 66 | func (e *Event) GetProperty(name string) (i interface{}, ok bool) { 67 | 68 | if e.EventData != nil { 69 | if i, ok = e.EventData[name]; ok { 70 | return 71 | } 72 | } 73 | 74 | if e.UserData != nil { 75 | if i, ok = e.UserData[name]; ok { 76 | return 77 | } 78 | } 79 | 80 | return 81 | } 82 | 83 | func (e *Event) GetPropertyString(name string) (string, bool) { 84 | if i, ok := e.GetProperty(name); ok { 85 | if s, ok := i.(string); ok { 86 | return s, ok 87 | } 88 | } 89 | return "", false 90 | } 91 | -------------------------------------------------------------------------------- /etw/filter.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "sync" 8 | 9 | "github.com/0xrawsec/golang-utils/datastructs" 10 | ) 11 | 12 | type IEvent interface { 13 | ProviderGUID() string 14 | EventID() uint16 15 | } 16 | 17 | type EventFilter interface { 18 | // Match must return true if the event has to be filtered in 19 | Match(IEvent) bool 20 | // Update adds a filter for/from a given provider 21 | Update(p *Provider) 22 | } 23 | 24 | type baseFilter struct { 25 | sync.RWMutex 26 | m map[string]*datastructs.Set 27 | } 28 | 29 | func (f *baseFilter) matchKey(key string, e IEvent) bool { 30 | f.RLock() 31 | defer f.RUnlock() 32 | 33 | // map is nil 34 | if f.m == nil { 35 | return true 36 | } 37 | 38 | // Filter is empty 39 | if len(f.m) == 0 { 40 | return true 41 | } 42 | 43 | if eventids, ok := f.m[key]; ok { 44 | if eventids.Len() > 0 { 45 | return eventids.Contains(e.EventID()) 46 | } 47 | return true 48 | } 49 | // we return true if no filter is found 50 | return true 51 | } 52 | 53 | // ProviderFilter structure to filter events based on Provider 54 | // definition 55 | type ProviderFilter struct { 56 | baseFilter 57 | } 58 | 59 | // NewProviderFilter creates a new ProviderFilter structure 60 | func NewProviderFilter() *ProviderFilter { 61 | f := ProviderFilter{} 62 | f.m = make(map[string]*datastructs.Set) 63 | return &f 64 | } 65 | 66 | // Match implements EventFilter 67 | func (f *ProviderFilter) Match(e IEvent) bool { 68 | return f.matchKey(e.ProviderGUID(), e) 69 | } 70 | 71 | // FromProvider implements EventFilter 72 | func (f *ProviderFilter) Update(p *Provider) { 73 | f.Lock() 74 | defer f.Unlock() 75 | if len(p.Filter) > 0 { 76 | s := datastructs.ToInterfaceSlice(p.Filter) 77 | f.m[p.GUID] = datastructs.NewInitSet(s...) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /etw/guid.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "fmt" 8 | "regexp" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | const ( 14 | nullGUIDStr = "{00000000-0000-0000-0000-000000000000}" 15 | ) 16 | 17 | var ( 18 | nullGUID = GUID{} 19 | ) 20 | 21 | /* 22 | typedef struct _GUID { 23 | DWORD Data1; 24 | WORD Data2; 25 | WORD Data3; 26 | BYTE Data4[8]; 27 | } GUID; 28 | */ 29 | 30 | // GUID structure 31 | type GUID struct { 32 | Data1 uint32 33 | Data2 uint16 34 | Data3 uint16 35 | Data4 [8]byte 36 | } 37 | 38 | // IsZero checks if GUID is all zeros 39 | func (g *GUID) IsZero() bool { 40 | return g.Equals(&nullGUID) 41 | } 42 | 43 | func (g *GUID) String() string { 44 | return fmt.Sprintf("{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", 45 | g.Data1, 46 | g.Data2, 47 | g.Data3, 48 | g.Data4[0], g.Data4[1], 49 | g.Data4[2], g.Data4[3], g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7]) 50 | } 51 | 52 | func (g *GUID) Equals(other *GUID) bool { 53 | return g.Data1 == other.Data1 && 54 | g.Data2 == other.Data2 && 55 | g.Data3 == other.Data3 && 56 | g.Data4[0] == other.Data4[0] && 57 | g.Data4[1] == other.Data4[1] && 58 | g.Data4[2] == other.Data4[2] && 59 | g.Data4[3] == other.Data4[3] && 60 | g.Data4[4] == other.Data4[4] && 61 | g.Data4[5] == other.Data4[5] && 62 | g.Data4[6] == other.Data4[6] && 63 | g.Data4[7] == other.Data4[7] 64 | } 65 | 66 | var ( 67 | guidRE = regexp.MustCompile(`^\{?[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}\}?$`) 68 | ) 69 | 70 | // MustParseGUIDFromString parses a guid string into a GUID struct or panics 71 | func MustParseGUIDFromString(sguid string) (guid *GUID) { 72 | var err error 73 | if guid, err = ParseGUID(sguid); err != nil { 74 | panic(err) 75 | } 76 | return 77 | } 78 | 79 | // ParseGUID parses a guid string into a GUID structure 80 | func ParseGUID(guid string) (g *GUID, err error) { 81 | var u uint64 82 | 83 | g = &GUID{} 84 | guid = strings.ToUpper(guid) 85 | if !guidRE.MatchString(guid) { 86 | return nil, fmt.Errorf("bad GUID format") 87 | } 88 | guid = strings.Trim(guid, "{}") 89 | sp := strings.Split(guid, "-") 90 | 91 | if u, err = strconv.ParseUint(sp[0], 16, 32); err != nil { 92 | return 93 | } 94 | g.Data1 = uint32(u) 95 | if u, err = strconv.ParseUint(sp[1], 16, 16); err != nil { 96 | return 97 | } 98 | g.Data2 = uint16(u) 99 | if u, err = strconv.ParseUint(sp[2], 16, 16); err != nil { 100 | return 101 | } 102 | g.Data3 = uint16(u) 103 | if u, err = strconv.ParseUint(sp[3], 16, 16); err != nil { 104 | return 105 | } 106 | g.Data4[0] = uint8(u >> 8) 107 | g.Data4[1] = uint8(u & 0xff) 108 | if u, err = strconv.ParseUint(sp[4], 16, 64); err != nil { 109 | return 110 | } 111 | g.Data4[2] = uint8((u >> 40)) 112 | g.Data4[3] = uint8((u >> 32) & 0xff) 113 | g.Data4[4] = uint8((u >> 24) & 0xff) 114 | g.Data4[5] = uint8((u >> 16) & 0xff) 115 | g.Data4[6] = uint8((u >> 8) & 0xff) 116 | g.Data4[7] = uint8(u & 0xff) 117 | 118 | return 119 | } 120 | -------------------------------------------------------------------------------- /etw/guid_test.go: -------------------------------------------------------------------------------- 1 | package etw 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/0xrawsec/toast" 9 | ) 10 | 11 | func TestGUID(t *testing.T) { 12 | t.Parallel() 13 | 14 | var g *GUID 15 | var err error 16 | 17 | tt := toast.FromT(t) 18 | 19 | // with curly brackets 20 | guid := "{45d8cccd-539f-4b72-a8b7-5c683142609a}" 21 | g, err = ParseGUID(guid) 22 | tt.CheckErr(err) 23 | tt.Assert(!g.IsZero()) 24 | tt.Assert(strings.EqualFold(guid, g.String())) 25 | 26 | guid = "54849625-5478-4994-a5ba-3e3b0328c30d" 27 | g, err = ParseGUID(guid) 28 | tt.CheckErr(err) 29 | tt.Assert(!g.IsZero()) 30 | tt.Assert(strings.EqualFold(fmt.Sprintf("{%s}", guid), g.String())) 31 | 32 | guid = "00000000-0000-0000-0000-000000000000" 33 | g, err = ParseGUID(guid) 34 | tt.CheckErr(err) 35 | tt.Assert(g.IsZero()) 36 | tt.Assert(strings.EqualFold(fmt.Sprintf("{%s}", guid), g.String())) 37 | } 38 | 39 | func TestGUIDEquality(t *testing.T) { 40 | t.Parallel() 41 | 42 | tt := toast.FromT(t) 43 | p := MustParseProvider("Microsoft-Windows-Kernel-File") 44 | g1 := MustParseGUIDFromString(p.GUID) 45 | g2 := MustParseGUIDFromString(p.GUID) 46 | 47 | tt.Assert(g1.Equals(g2)) 48 | 49 | // testing Data1 50 | g2.Data1++ 51 | tt.Assert(!g1.Equals(g2)) 52 | 53 | // testing Data2 54 | g2 = MustParseGUIDFromString(p.GUID) 55 | g2.Data2++ 56 | tt.Assert(!g1.Equals(g2)) 57 | 58 | // testing Data3 59 | g2 = MustParseGUIDFromString(p.GUID) 60 | g2.Data3++ 61 | tt.Assert(!g1.Equals(g2)) 62 | 63 | // testing Data4 64 | for i := 0; i < 8; i++ { 65 | g2 = MustParseGUIDFromString(p.GUID) 66 | g2.Data4[i]++ 67 | tt.Assert(!g1.Equals(g2)) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /etw/kernel_provider_test.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/0xrawsec/toast" 10 | ) 11 | 12 | func hasFlag(flags, flag uint32) bool { 13 | return flags&flag == flag 14 | } 15 | 16 | func TestKernelProviders(t *testing.T) { 17 | tt := toast.FromT(t) 18 | 19 | for _, p := range KernelProviders { 20 | tt.Assert(IsKernelProvider(p.Name)) 21 | tt.Assert(IsKernelProvider(p.GUID)) 22 | 23 | tt.Assert(GetKernelProviderFlags(p.Name) == p.Flags) 24 | // some providers have the same GUID so we have to check flags contains p.Flags 25 | tt.Assert(GetKernelProviderFlags(p.GUID)&p.Flags == p.Flags) 26 | } 27 | 28 | combinedFlags := GetKernelProviderFlags("ALPC", "ImageLoad") 29 | tt.Assert(combinedFlags != EVENT_TRACE_FLAG_ALPC) 30 | tt.Assert(hasFlag(combinedFlags, EVENT_TRACE_FLAG_ALPC)) 31 | tt.Assert(combinedFlags != EVENT_TRACE_FLAG_IMAGE_LOAD) 32 | tt.Assert(hasFlag(combinedFlags, EVENT_TRACE_FLAG_IMAGE_LOAD)) 33 | } 34 | -------------------------------------------------------------------------------- /etw/kernel_providers.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import "strings" 7 | 8 | type ProviderDefinition struct { 9 | Name string 10 | Kernel bool 11 | GUID string 12 | Flags uint32 13 | } 14 | 15 | var ( 16 | KernelProviders = []ProviderDefinition{ 17 | // https://docs.microsoft.com/en-us/windows/win32/etw/alpc 18 | {Name: "ALPC", 19 | Kernel: true, 20 | GUID: "{45d8cccd-539f-4b72-a8b7-5c683142609a}", 21 | Flags: EVENT_TRACE_FLAG_ALPC}, 22 | //{Name: "ApplicationVerifier", Kernel: true, GUID: "{78d14f17-0105-46d7-bfff-6fbea2f3f358}"}, 23 | {Name: "DbgPrint", 24 | Kernel: true, 25 | GUID: "{13976d09-a327-438c-950b-7f03192815c7}", 26 | Flags: EVENT_TRACE_FLAG_DBGPRINT}, 27 | // https://docs.microsoft.com/en-us/windows/win32/etw/diskio 28 | {Name: "DiskIo", 29 | Kernel: true, 30 | GUID: "{3d6fa8d4-fe05-11d0-9dda-00c04fd7ba7c}", 31 | Flags: EVENT_TRACE_FLAG_DISK_IO}, 32 | {Name: "DiskIoInit", 33 | Kernel: true, 34 | GUID: "{3d6fa8d4-fe05-11d0-9dda-00c04fd7ba7c}", 35 | Flags: EVENT_TRACE_FLAG_DISK_IO_INIT}, 36 | {Name: "Driver", 37 | Kernel: true, 38 | GUID: "{3d6fa8d4-fe05-11d0-9dda-00c04fd7ba7c}", 39 | Flags: EVENT_TRACE_FLAG_DRIVER}, 40 | //{Name: "DiskPerf", Kernel: true, GUID: "{bdd865d1-d7c1-11d0-a501-00a0c9062910}"}, 41 | //{Name: "DriverVerifier", Kernel: true, GUID: "{d56ca431-61bf-4904-a621-00e0381e4dde"}, 42 | //{Name: "EventLog", Kernel: true, GUID: "{b16f9f5e-b3da-4027-9318-adf2b79df73b}"}, 43 | //{Name: "EventTraceConfig", Kernel: true, GUID: "{01853a65-418f-4f36-aefc-dc0f1d2fd235}"}, 44 | // https://docs.microsoft.com/en-us/windows/win32/etw/fileio 45 | {Name: "FileIo", 46 | Kernel: true, 47 | GUID: "{90cbdc39-4a3e-11d1-84f4-0000f80464e3}", 48 | Flags: EVENT_TRACE_FLAG_FILE_IO}, 49 | {Name: "FileIoInit", 50 | Kernel: true, 51 | GUID: "{90cbdc39-4a3e-11d1-84f4-0000f80464e3}", 52 | Flags: EVENT_TRACE_FLAG_FILE_IO_INIT}, 53 | //{Name: "GenericMessage", Kernel: true, GUID: "{8d40301f-ab4a-11d2-9a93-00805f85d7c6}"}, 54 | //{Name: "GlobalLogger", Kernel: true, GUID: "{e8908abc-aa84-11d2-9a93-00805f85d7c6}"}, 55 | //{Name: "HardFault", Kernel: true, GUID: "{3d6fa8d2-fe05-11d0-9dda-00c04fd7ba7c}"}, 56 | // https://docs.microsoft.com/en-us/windows/win32/etw/image 57 | {Name: "ImageLoad", 58 | Kernel: true, 59 | GUID: "{2cb15d1d-5fc1-11d2-abe1-00a0c911f518}", 60 | Flags: EVENT_TRACE_FLAG_IMAGE_LOAD}, 61 | //{Name: "MsSystemInformation", Kernel: true, GUID: "{98a2b9d7-94dd-496a-847e-67a5557a59f2}"}, 62 | // https://docs.microsoft.com/en-us/windows/win32/etw/pagefault-v2 63 | {Name: "MemoryPageFault", 64 | Kernel: true, 65 | GUID: "{3d6fa8d3-fe05-11d0-9dda-00c04fd7ba7c}", 66 | Flags: EVENT_TRACE_FLAG_MEMORY_PAGE_FAULTS}, 67 | {Name: "MemoryHardFault", 68 | Kernel: true, 69 | GUID: "{3d6fa8d3-fe05-11d0-9dda-00c04fd7ba7c}", 70 | Flags: EVENT_TRACE_FLAG_MEMORY_HARD_FAULTS}, 71 | {Name: "VirtualAlloc", 72 | Kernel: true, 73 | GUID: "{3d6fa8d3-fe05-11d0-9dda-00c04fd7ba7c}", 74 | Flags: EVENT_TRACE_FLAG_VIRTUAL_ALLOC}, 75 | // https://docs.microsoft.com/en-us/windows/win32/etw/process 76 | {Name: "DPC", 77 | Kernel: true, 78 | GUID: "{ce1dbfb4-137e-4da6-87b0-3f59aa102cbc}", 79 | Flags: EVENT_TRACE_FLAG_DPC}, 80 | {Name: "Interrupt", 81 | Kernel: true, 82 | GUID: "{ce1dbfb4-137e-4da6-87b0-3f59aa102cbc}", 83 | Flags: EVENT_TRACE_FLAG_INTERRUPT}, 84 | {Name: "Profile", 85 | Kernel: true, 86 | GUID: "{ce1dbfb4-137e-4da6-87b0-3f59aa102cbc}", 87 | Flags: EVENT_TRACE_FLAG_PROFILE}, 88 | {Name: "Syscall", 89 | Kernel: true, 90 | GUID: "{ce1dbfb4-137e-4da6-87b0-3f59aa102cbc}", 91 | Flags: EVENT_TRACE_FLAG_SYSTEMCALL}, 92 | // https://docs.microsoft.com/en-us/windows/win32/etw/process 93 | {Name: "Process", 94 | Kernel: true, 95 | GUID: "{3d6fa8d0-fe05-11d0-9dda-00c04fd7ba7c}", 96 | Flags: EVENT_TRACE_FLAG_PROCESS}, 97 | {Name: "ProcessCounters", 98 | Kernel: true, 99 | GUID: "{3d6fa8d0-fe05-11d0-9dda-00c04fd7ba7c}", 100 | Flags: EVENT_TRACE_FLAG_PROCESS_COUNTERS}, 101 | // https://docs.microsoft.com/en-us/windows/win32/etw/registry 102 | {Name: "Registry", 103 | Kernel: true, 104 | GUID: "{ae53722e-c863-11d2-8659-00c04fa321a1}", 105 | Flags: EVENT_TRACE_FLAG_REGISTRY}, 106 | // https://docs.microsoft.com/en-us/windows/win32/etw/splitio 107 | {Name: "SplitIo", 108 | Kernel: true, 109 | GUID: "{d837ca92-12b9-44a5-ad6a-3a65b3578aa8}", 110 | Flags: EVENT_TRACE_FLAG_SPLIT_IO}, 111 | // https://docs.microsoft.com/en-us/windows/win32/etw/tcpip 112 | {Name: "TcpIp", 113 | Kernel: true, 114 | GUID: "{9a280ac0-c8e0-11d1-84e2-00c04fb998a2}", 115 | Flags: EVENT_TRACE_FLAG_NETWORK_TCPIP}, 116 | //{Name: "ThermalZone", Kernel: true, GUID: "{a1bc18c0-a7c8-11d1-bf3c-00a0c9062910}"}, 117 | // https://docs.microsoft.com/en-us/windows/win32/etw/thread 118 | {Name: "Thread", 119 | Kernel: true, 120 | GUID: "{3d6fa8d1-fe05-11d0-9dda-00c04fd7ba7c}", 121 | Flags: EVENT_TRACE_FLAG_THREAD}, 122 | //{Name: "TraceError", Kernel: true, GUID: "{398191dc-2da7-11d3-8b98-00805f85d7c6}"}, 123 | // https://docs.microsoft.com/en-us/windows/win32/etw/udpip 124 | {Name: "UdpIp", 125 | Kernel: true, GUID: "{bf3a50c5-a9c9-4988-a005-2df0b7c80f80}", 126 | Flags: EVENT_TRACE_FLAG_NETWORK_TCPIP}, 127 | 128 | //{Name: "WmiEventLogger", Kernel: true, GUID: "{44608a51-1851-4456-98b2-b300e931ee41}"} 129 | } 130 | ) 131 | 132 | func IsKernelProvider(term string) bool { 133 | for _, pd := range KernelProviders { 134 | if strings.EqualFold(term, pd.Name) || term == pd.GUID { 135 | return true 136 | } 137 | } 138 | return false 139 | } 140 | 141 | func GetKernelProviderFlags(terms ...string) (flags uint32) { 142 | for _, t := range terms { 143 | for _, pd := range KernelProviders { 144 | if strings.EqualFold(t, pd.Name) || t == pd.GUID { 145 | flags |= pd.Flags 146 | } 147 | } 148 | 149 | } 150 | return 151 | } 152 | -------------------------------------------------------------------------------- /etw/mof.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | type MofClass struct { 7 | // Class name 8 | Name string 9 | // Serves as base to compute event id 10 | BaseId uint16 11 | } 12 | 13 | var ( 14 | // The final event id of Mof Events is computed 15 | // by BaseId + Opcode. As Opcode is uint8 we jump 16 | // BaseIds every 0xff so that we do not overlap event 17 | // ids between classes 18 | MofClassMapping = map[uint32]MofClass{ 19 | /*45d8cccd-539f-4b72-a8b7-5c683142609a*/ 20 | 1171836109: {Name: "ALPC", BaseId: 0}, 21 | /*78d14f17-0105-46d7-bfff-6fbea2f3f358*/ 22 | 2026983191: {Name: "ApplicationVerifier", BaseId: 255}, 23 | /*13976d09-a327-438c-950b-7f03192815c7*/ 24 | 328690953: {Name: "DbgPrint", BaseId: 510}, 25 | /*3d6fa8d4-fe05-11d0-9dda-00c04fd7ba7c*/ 26 | 1030727892: {Name: "DiskIo", BaseId: 765}, 27 | /*bdd865d1-d7c1-11d0-a501-00a0c9062910*/ 28 | 3185075665: {Name: "DiskPerf", BaseId: 1020}, 29 | /*d56ca431-61bf-4904-a621-00e0381e4dde*/ 30 | 3580666929: {Name: "DriverVerifier", BaseId: 1275}, 31 | /*b16f9f5e-b3da-4027-9318-adf2b79df73b*/ 32 | 2976882526: {Name: "EventLog", BaseId: 1530}, 33 | /*01853a65-418f-4f36-aefc-dc0f1d2fd235*/ 34 | 25508453: {Name: "EventTraceConfig", BaseId: 1785}, 35 | /*90cbdc39-4a3e-11d1-84f4-0000f80464e3*/ 36 | 2429279289: {Name: "FileIo", BaseId: 2040}, 37 | /*8d40301f-ab4a-11d2-9a93-00805f85d7c6*/ 38 | 2369794079: {Name: "GenericMessage", BaseId: 2295}, 39 | /*e8908abc-aa84-11d2-9a93-00805f85d7c6*/ 40 | 3901786812: {Name: "GlobalLogger", BaseId: 2550}, 41 | /*3d6fa8d2-fe05-11d0-9dda-00c04fd7ba7c*/ 42 | 1030727890: {Name: "HardFault", BaseId: 2805}, 43 | /*2cb15d1d-5fc1-11d2-abe1-00a0c911f518*/ 44 | 749821213: {Name: "ImageLoad", BaseId: 3060}, 45 | /*98a2b9d7-94dd-496a-847e-67a5557a59f2*/ 46 | 2560801239: {Name: "MsSystemInformation", BaseId: 3315}, 47 | /*3d6fa8d3-fe05-11d0-9dda-00c04fd7ba7c*/ 48 | 1030727891: {Name: "PageFault", BaseId: 3570}, 49 | /*ce1dbfb4-137e-4da6-87b0-3f59aa102cbc*/ 50 | 3458056116: {Name: "PerfInfo", BaseId: 3825}, 51 | /*3d6fa8d0-fe05-11d0-9dda-00c04fd7ba7c*/ 52 | 1030727888: {Name: "Process", BaseId: 4080}, 53 | /*ae53722e-c863-11d2-8659-0c04fa321a1*/ 54 | 2924704302: {Name: "Registry", BaseId: 4335}, 55 | /*d837ca92-12b9-44a5-ad6a-3a65b3578aa8*/ 56 | 3627534994: {Name: "SplitIo", BaseId: 4590}, 57 | /*9a280ac0-c8e0-11d1-84e2-00c04fb998a2*/ 58 | 2586315456: {Name: "TcpIp", BaseId: 4845}, 59 | /*a1bc18c0-a7c8-11d1-bf3c-00a0c9062910*/ 60 | 2713458880: {Name: "ThermalZone", BaseId: 5100}, 61 | /*3d6fa8d1-fe05-11d0-9dda-00c04fd7ba7c*/ 62 | 1030727889: {Name: "Thread", BaseId: 5355}, 63 | /*398191dc-2da7-11d3-8b98-00805f85d7c6*/ 64 | 964792796: {Name: "TraceError", BaseId: 5610}, 65 | /*bf3a50c5-a9c9-4988-a005-2df0b7c80f80*/ 66 | 3208270021: {Name: "UdpIp", BaseId: 5865}, 67 | /*44608a51-1851-4456-98b2-b300e931ee41*/ 68 | 1147177553: {Name: "WmiEventLogger", BaseId: 6120}, 69 | /*68fdd900-4a3e-11d1-84f4-0000f80464e3*/ 70 | 0x68fdd900: {Name: "EventTraceEvent", BaseId: 6375}, 71 | } 72 | ) 73 | -------------------------------------------------------------------------------- /etw/producer.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | const ( 12 | NtKernelLogger = "NT Kernel Logger" 13 | // 0x9e814aad, 0x3204, 0x11d2, 0x9a, 0x82, 0x00, 0x60, 0x08, 0xa8, 0x69, 0x39 14 | ) 15 | 16 | var ( 17 | systemTraceControlGuid = MustParseGUIDFromString("{9E814AAD-3204-11D2-9A82-006008A86939}") 18 | ) 19 | 20 | type Session interface { 21 | TraceName() string 22 | Providers() []Provider 23 | } 24 | 25 | type RealTimeSession struct { 26 | properties *EventTraceProperties 27 | sessionHandle syscall.Handle 28 | 29 | traceName string 30 | providers []Provider 31 | } 32 | 33 | // NewRealTimeSession creates a new ETW session to receive events 34 | // in real time 35 | func NewRealTimeSession(name string) (p *RealTimeSession) { 36 | p = &RealTimeSession{} 37 | p.properties = NewRealTimeEventTraceSessionProperties(name) 38 | p.traceName = name 39 | p.providers = make([]Provider, 0) 40 | return 41 | } 42 | 43 | // NewKernelRealTimeSession creates a new ETW session to receive 44 | // NT Kernel Logger events in real time 45 | func NewKernelRealTimeSession(flags ...uint32) (p *RealTimeSession) { 46 | p = NewRealTimeSession(NtKernelLogger) 47 | // guid must be set for Kernel Session 48 | p.properties.Wnode.Guid = *systemTraceControlGuid 49 | for _, flag := range flags { 50 | p.properties.EnableFlags |= flag 51 | } 52 | return 53 | } 54 | 55 | // IsStarted returns true if the session is already started 56 | func (p *RealTimeSession) IsStarted() bool { 57 | return p.sessionHandle != 0 58 | } 59 | 60 | // Start starts the session 61 | func (p *RealTimeSession) Start() (err error) { 62 | var u16TraceName *uint16 63 | 64 | if u16TraceName, err = syscall.UTF16PtrFromString(p.traceName); err != nil { 65 | return err 66 | } 67 | 68 | if !p.IsStarted() { 69 | if err = StartTrace(&p.sessionHandle, u16TraceName, p.properties); err != nil { 70 | // we handle the case where the trace already exists 71 | if err == ERROR_ALREADY_EXISTS { 72 | // we have to use a copy of properties as ControlTrace modifies 73 | // the structure and if we don't do that we cannot StartTrace later 74 | prop := *p.properties 75 | // we close the trace first 76 | ControlTrace(0, u16TraceName, &prop, EVENT_TRACE_CONTROL_STOP) 77 | return StartTrace(&p.sessionHandle, u16TraceName, p.properties) 78 | } 79 | return 80 | } 81 | } 82 | 83 | return 84 | } 85 | 86 | // EnableProvider enables the session to receive events from a given provider 87 | func (p *RealTimeSession) EnableProvider(prov Provider) (err error) { 88 | var guid *GUID 89 | 90 | // If the trace is not started yet we have to start it 91 | // otherwise we cannot enable provider 92 | if !p.IsStarted() { 93 | if err = p.Start(); err != nil { 94 | return 95 | } 96 | } 97 | 98 | if guid, err = ParseGUID(prov.GUID); err != nil { 99 | return 100 | } 101 | 102 | params := EnableTraceParameters{ 103 | Version: 2, 104 | // Does not seem to bring valuable information 105 | //EnableProperty: EVENT_ENABLE_PROPERTY_PROCESS_START_KEY, 106 | } 107 | 108 | if len(prov.Filter) > 0 { 109 | fds := prov.BuildFilterDesc() 110 | if len(fds) > 0 { 111 | params.EnableFilterDesc = (*EventFilterDescriptor)(unsafe.Pointer(&fds[0])) 112 | params.FilterDescCount = uint32(len(fds)) 113 | } 114 | } 115 | 116 | if err = EnableTraceEx2( 117 | p.sessionHandle, 118 | guid, 119 | EVENT_CONTROL_CODE_ENABLE_PROVIDER, 120 | prov.EnableLevel, 121 | prov.MatchAnyKeyword, 122 | prov.MatchAllKeyword, 123 | 0, 124 | ¶ms, 125 | ); err != nil { 126 | return 127 | } 128 | 129 | p.providers = append(p.providers, prov) 130 | 131 | return 132 | } 133 | 134 | // TraceName implements Session interface 135 | func (p *RealTimeSession) TraceName() string { 136 | return p.traceName 137 | } 138 | 139 | // Providers implements Session interface 140 | func (p *RealTimeSession) Providers() []Provider { 141 | return p.providers 142 | } 143 | 144 | // Stop stops the session 145 | func (p *RealTimeSession) Stop() error { 146 | return ControlTrace(p.sessionHandle, nil, p.properties, EVENT_TRACE_CONTROL_STOP) 147 | } 148 | -------------------------------------------------------------------------------- /etw/provider.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "fmt" 8 | "strconv" 9 | "strings" 10 | "unsafe" 11 | ) 12 | 13 | var ( 14 | providers ProviderMap 15 | 16 | DefaultProvider = Provider{EnableLevel: 0xff} 17 | 18 | // Error returned when a provider is not found on the system 19 | ErrUnkownProvider = fmt.Errorf("unknown provider") 20 | ) 21 | 22 | type ProviderMap map[string]*Provider 23 | 24 | type Provider struct { 25 | GUID string 26 | Name string 27 | EnableLevel uint8 28 | MatchAnyKeyword uint64 29 | MatchAllKeyword uint64 30 | Filter []uint16 31 | } 32 | 33 | // IsZero returns true if the provider is empty 34 | func (p *Provider) IsZero() bool { 35 | return p.GUID == "" 36 | } 37 | 38 | func (p *Provider) eventIDFilterDescriptor() (d EventFilterDescriptor) { 39 | 40 | efeid := AllocEventFilterEventID(p.Filter) 41 | efeid.FilterIn = 0x1 42 | 43 | d = EventFilterDescriptor{ 44 | Ptr: uint64(uintptr(unsafe.Pointer(efeid))), 45 | Size: uint32(efeid.Size()), 46 | Type: EVENT_FILTER_TYPE_EVENT_ID, 47 | } 48 | 49 | return 50 | } 51 | 52 | func (p *Provider) BuildFilterDesc() (fd []EventFilterDescriptor) { 53 | 54 | fd = append(fd, p.eventIDFilterDescriptor()) 55 | 56 | return 57 | } 58 | 59 | // MustParseProvider parses a provider string or panic 60 | func MustParseProvider(s string) (p Provider) { 61 | var err error 62 | if p, err = ParseProvider(s); err != nil { 63 | panic(err) 64 | } 65 | return 66 | } 67 | 68 | // IsKnownProvider returns true if the provider is known 69 | func IsKnownProvider(p string) bool { 70 | prov := ResolveProvider(p) 71 | return !prov.IsZero() 72 | } 73 | 74 | // ParseProvider parses a string and returns a provider. 75 | // The returned provider is initialized from DefaultProvider. 76 | // Format (Name|GUID) string:EnableLevel uint8:Event IDs comma sep string:MatchAnyKeyword uint16:MatchAllKeyword uint16 77 | // Example: Microsoft-Windows-Kernel-File:0xff:13,14:0x80 78 | func ParseProvider(s string) (p Provider, err error) { 79 | var u uint64 80 | 81 | split := strings.Split(s, ":") 82 | for i := 0; i < len(split); i++ { 83 | chunk := split[i] 84 | switch i { 85 | case 0: 86 | p = ResolveProvider(chunk) 87 | if p.IsZero() { 88 | err = fmt.Errorf("%w %s", ErrUnkownProvider, chunk) 89 | return 90 | } 91 | case 1: 92 | if chunk == "" { 93 | break 94 | } 95 | // parsing EnableLevel 96 | if u, err = strconv.ParseUint(chunk, 0, 8); err != nil { 97 | err = fmt.Errorf("failed to parse EnableLevel: %w", err) 98 | return 99 | } else { 100 | p.EnableLevel = uint8(u) 101 | } 102 | case 2: 103 | if chunk == "" { 104 | break 105 | } 106 | // parsing event ids 107 | for _, eid := range strings.Split(chunk, ",") { 108 | if u, err = strconv.ParseUint(eid, 0, 16); err != nil { 109 | err = fmt.Errorf("failed to parse EventID: %w", err) 110 | return 111 | } else { 112 | p.Filter = append(p.Filter, uint16(u)) 113 | } 114 | } 115 | case 3: 116 | if chunk == "" { 117 | break 118 | } 119 | 120 | // parsing MatchAnyKeyword 121 | if u, err = strconv.ParseUint(chunk, 0, 64); err != nil { 122 | err = fmt.Errorf("failed to parse MatchAnyKeyword: %w", err) 123 | return 124 | } else { 125 | p.MatchAnyKeyword = u 126 | } 127 | case 4: 128 | if chunk == "" { 129 | break 130 | } 131 | 132 | // parsing MatchAllKeyword 133 | if u, err = strconv.ParseUint(chunk, 0, 64); err != nil { 134 | err = fmt.Errorf("failed to parse MatchAllKeyword: %w", err) 135 | return 136 | } else { 137 | p.MatchAllKeyword = u 138 | } 139 | default: 140 | return 141 | } 142 | } 143 | return 144 | } 145 | 146 | // EnumerateProviders returns a ProviderMap containing available providers 147 | // keys are both provider's GUIDs and provider's names 148 | func EnumerateProviders() (m ProviderMap) { 149 | var buf *ProviderEnumerationInfo 150 | size := uint32(1) 151 | for { 152 | tmp := make([]byte, size) 153 | buf = (*ProviderEnumerationInfo)(unsafe.Pointer(&tmp[0])) 154 | if err := TdhEnumerateProviders(buf, &size); err != ERROR_INSUFFICIENT_BUFFER { 155 | break 156 | } 157 | } 158 | m = make(ProviderMap) 159 | startProvEnumInfo := uintptr(unsafe.Pointer(buf)) 160 | it := uintptr(unsafe.Pointer(&buf.TraceProviderInfoArray[0])) 161 | for i := uintptr(0); i < uintptr(buf.NumberOfProviders); i++ { 162 | ptpi := (*TraceProviderInfo)(unsafe.Pointer(it + i*unsafe.Sizeof(buf.TraceProviderInfoArray[0]))) 163 | guid := ptpi.ProviderGuid.String() 164 | name := UTF16AtOffsetToString(startProvEnumInfo, uintptr(ptpi.ProviderNameOffset)) 165 | // We use a default provider here 166 | p := DefaultProvider 167 | p.GUID = guid 168 | p.Name = name 169 | m[name] = &p 170 | m[guid] = &p 171 | } 172 | return 173 | } 174 | 175 | // ResolveProvider return a Provider structure given a GUID or 176 | // a provider name as input 177 | func ResolveProvider(s string) (p Provider) { 178 | 179 | if providers == nil { 180 | providers = EnumerateProviders() 181 | } 182 | 183 | if g, err := ParseGUID(s); err == nil { 184 | s = g.String() 185 | } 186 | 187 | if prov, ok := providers[s]; ok { 188 | // search provider by name 189 | return *prov 190 | } 191 | 192 | return 193 | } 194 | -------------------------------------------------------------------------------- /etw/tdh.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | /* 12 | TdhEnumerateProviderFieldInformation API wrapper generated from prototype 13 | ULONG __stdcall TdhEnumerateProviderFieldInformation( 14 | LPGUID pGuid, 15 | EVENT_FIELD_TYPE EventFieldType, 16 | PPROVIDER_FIELD_INFOARRAY pBuffer, 17 | ULONG *pBufferSize ); 18 | 19 | Tested: NOK 20 | */ 21 | func TdhEnumerateProviderFieldInformation( 22 | pGuid *GUID, 23 | eventFieldType int, 24 | pBuffer *ProviderFieldInfoArray, 25 | pBufferSize *uint32) error { 26 | r1, _, _ := tdhEnumerateProviderFieldInformation.Call( 27 | uintptr(unsafe.Pointer(pGuid)), 28 | uintptr(eventFieldType), 29 | uintptr(unsafe.Pointer(pBuffer)), 30 | uintptr(unsafe.Pointer(pBufferSize))) 31 | if r1 == 0 { 32 | return nil 33 | } 34 | return syscall.Errno(r1) 35 | } 36 | 37 | /* 38 | TdhEnumerateProviders API wrapper generated from prototype 39 | ULONG __stdcall TdhEnumerateProviders( 40 | PPROVIDER_ENUMERATION_INFO pBuffer, 41 | ULONG *pBufferSize ); 42 | 43 | Tested: NOK 44 | */ 45 | func TdhEnumerateProviders( 46 | pBuffer *ProviderEnumerationInfo, 47 | pBufferSize *uint32) error { 48 | r1, _, _ := tdhEnumerateProviders.Call( 49 | uintptr(unsafe.Pointer(pBuffer)), 50 | uintptr(unsafe.Pointer(pBufferSize))) 51 | if r1 == 0 { 52 | return nil 53 | } 54 | return syscall.Errno(r1) 55 | } 56 | 57 | /* 58 | TdhGetEventInformation API wrapper generated from prototype 59 | ULONG __stdcall TdhGetEventInformation( 60 | PEVENT_RECORD pEvent, 61 | ULONG TdhContextCount, 62 | PTDH_CONTEXT pTdhContext, 63 | PTRACE_EVENT_INFO pBuffer, 64 | ULONG *pBufferSize ); 65 | 66 | Tested: OK 67 | */ 68 | func TdhGetEventInformation(pEvent *EventRecord, 69 | tdhContextCount uint32, 70 | pTdhContext *TdhContext, 71 | pBuffer *TraceEventInfo, 72 | pBufferSize *uint32) error { 73 | r1, _, _ := tdhGetEventInformation.Call( 74 | uintptr(unsafe.Pointer(pEvent)), 75 | uintptr(tdhContextCount), 76 | uintptr(unsafe.Pointer(pTdhContext)), 77 | uintptr(unsafe.Pointer(pBuffer)), 78 | uintptr(unsafe.Pointer(pBufferSize))) 79 | if r1 == 0 { 80 | return nil 81 | } 82 | return syscall.Errno(r1) 83 | } 84 | 85 | /* 86 | TdhGetEventMapInformation API wrapper generated from prototype 87 | ULONG __stdcall TdhGetEventMapInformation( 88 | PEVENT_RECORD pEvent, 89 | LPWSTR pMapName, 90 | PEVENT_MAP_INFO pBuffer, 91 | ULONG *pBufferSize ); 92 | 93 | Tested: OK 94 | */ 95 | func TdhGetEventMapInformation(pEvent *EventRecord, 96 | pMapName *uint16, 97 | pBuffer *EventMapInfo, 98 | pBufferSize *uint32) error { 99 | r1, _, _ := tdhGetEventMapInformation.Call( 100 | uintptr(unsafe.Pointer(pEvent)), 101 | uintptr(unsafe.Pointer(pMapName)), 102 | uintptr(unsafe.Pointer(pBuffer)), 103 | uintptr(unsafe.Pointer(pBufferSize))) 104 | if r1 == 0 { 105 | return nil 106 | } 107 | return syscall.Errno(r1) 108 | } 109 | 110 | /* 111 | TdhGetProperty API wrapper generated from prototype 112 | ULONG __stdcall TdhGetProperty( 113 | PEVENT_RECORD pEvent, 114 | ULONG TdhContextCount, 115 | PTDH_CONTEXT pTdhContext, 116 | ULONG PropertyDataCount, 117 | PPROPERTY_DATA_DESCRIPTOR pPropertyData, 118 | ULONG BufferSize, 119 | PBYTE pBuffer ); 120 | 121 | Tested: OK 122 | */ 123 | func TdhGetProperty(pEvent *EventRecord, 124 | tdhContextCount uint32, 125 | pTdhContext *TdhContext, 126 | propertyDataCount uint32, 127 | pPropertyData *PropertyDataDescriptor, 128 | bufferSize uint32, 129 | pBuffer *byte) error { 130 | r1, _, _ := tdhGetProperty.Call( 131 | uintptr(unsafe.Pointer(pEvent)), 132 | uintptr(tdhContextCount), 133 | uintptr(unsafe.Pointer(pTdhContext)), 134 | uintptr(propertyDataCount), 135 | uintptr(unsafe.Pointer(pPropertyData)), 136 | uintptr(bufferSize), 137 | uintptr(unsafe.Pointer(pBuffer))) 138 | if r1 == 0 { 139 | return nil 140 | } 141 | return syscall.Errno(r1) 142 | } 143 | 144 | /* 145 | TdhGetPropertySize API wrapper generated from prototype 146 | ULONG __stdcall TdhGetPropertySize( 147 | PEVENT_RECORD pEvent, 148 | ULONG TdhContextCount, 149 | PTDH_CONTEXT pTdhContext, 150 | ULONG PropertyDataCount, 151 | PPROPERTY_DATA_DESCRIPTOR pPropertyData, 152 | ULONG *pPropertySize ); 153 | 154 | Tested: OK 155 | */ 156 | func TdhGetPropertySize(pEvent *EventRecord, 157 | tdhContextCount uint32, 158 | pTdhContext *TdhContext, 159 | propertyDataCount uint32, 160 | pPropertyData *PropertyDataDescriptor, 161 | pPropertySize *uint32) error { 162 | r1, _, _ := tdhGetPropertySize.Call( 163 | uintptr(unsafe.Pointer(pEvent)), 164 | uintptr(tdhContextCount), 165 | uintptr(unsafe.Pointer(pTdhContext)), 166 | uintptr(propertyDataCount), 167 | uintptr(unsafe.Pointer(pPropertyData)), 168 | uintptr(unsafe.Pointer(pPropertySize))) 169 | if r1 == 0 { 170 | return nil 171 | } 172 | return syscall.Errno(r1) 173 | } 174 | 175 | /* 176 | TdhQueryProviderFieldInformation API wrapper generated from prototype 177 | ULONG __stdcall TdhQueryProviderFieldInformation( 178 | LPGUID pGuid, 179 | ULONGLONG EventFieldValue, 180 | EVENT_FIELD_TYPE EventFieldType, 181 | PPROVIDER_FIELD_INFOARRAY pBuffer, 182 | ULONG *pBufferSize ); 183 | 184 | Tested: NOK 185 | */ 186 | func TdhQueryProviderFieldInformation( 187 | pGuid *GUID, 188 | eventFieldValue uint64, 189 | eventFieldType int, 190 | pBuffer *ProviderFieldInfoArray, 191 | pBufferSize *uint32) error { 192 | r1, _, _ := tdhQueryProviderFieldInformation.Call( 193 | uintptr(unsafe.Pointer(pGuid)), 194 | uintptr(eventFieldValue), 195 | uintptr(eventFieldType), 196 | uintptr(unsafe.Pointer(pBuffer)), 197 | uintptr(unsafe.Pointer(pBufferSize))) 198 | if r1 == 0 { 199 | return nil 200 | } 201 | return syscall.Errno(r1) 202 | } 203 | 204 | /* 205 | TdhFormatProperty API wrapper generated from prototype 206 | TDHSTATUS TdhFormatProperty( 207 | PTRACE_EVENT_INFO EventInfo, 208 | PEVENT_MAP_INFO MapInfo, 209 | ULONG PointerSize, 210 | USHORT PropertyInType, 211 | USHORT PropertyOutType, 212 | USHORT PropertyLength, 213 | USHORT UserDataLength, 214 | PBYTE UserData, 215 | PULONG BufferSize, 216 | PWCHAR Buffer, 217 | PUSHORT UserDataConsumed ); 218 | 219 | Tested: OK 220 | */ 221 | func TdhFormatProperty( 222 | eventInfo *TraceEventInfo, 223 | mapInfo *EventMapInfo, 224 | pointerSize uint32, 225 | propertyInType uint16, 226 | propertyOutType uint16, 227 | propertyLength uint16, 228 | userDataLength uint16, 229 | userData *byte, 230 | bufferSize *uint32, 231 | buffer *uint16, 232 | userDataConsumed *uint16) error { 233 | r1, _, _ := tdhFormatProperty.Call( 234 | uintptr(unsafe.Pointer(eventInfo)), 235 | uintptr(unsafe.Pointer(mapInfo)), 236 | uintptr(pointerSize), 237 | uintptr(propertyInType), 238 | uintptr(propertyOutType), 239 | uintptr(propertyLength), 240 | uintptr(userDataLength), 241 | uintptr(unsafe.Pointer(userData)), 242 | uintptr(unsafe.Pointer(bufferSize)), 243 | uintptr(unsafe.Pointer(buffer)), 244 | uintptr(unsafe.Pointer(userDataConsumed))) 245 | if r1 == 0 { 246 | return nil 247 | } 248 | return syscall.Errno(r1) 249 | } 250 | -------------------------------------------------------------------------------- /etw/tdh_exports.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "syscall" 8 | ) 9 | 10 | var ( 11 | tdh = syscall.NewLazyDLL("tdh.dll") 12 | tdhEnumerateProviderFieldInformation = tdh.NewProc("TdhEnumerateProviderFieldInformation") 13 | tdhEnumerateProviderFilters = tdh.NewProc("TdhEnumerateProviderFilters") 14 | tdhEnumerateProviders = tdh.NewProc("TdhEnumerateProviders") 15 | tdhEnumerateRemoteWBEMProviderFieldInformation = tdh.NewProc("TdhEnumerateRemoteWBEMProviderFieldInformation") 16 | tdhEnumerateRemoteWBEMProviders = tdh.NewProc("TdhEnumerateRemoteWBEMProviders") 17 | tdhFormatProperty = tdh.NewProc("TdhFormatProperty") 18 | tdhGetAllEventsInformation = tdh.NewProc("TdhGetAllEventsInformation") 19 | tdhGetEventInformation = tdh.NewProc("TdhGetEventInformation") 20 | tdhGetEventMapInformation = tdh.NewProc("TdhGetEventMapInformation") 21 | tdhGetProperty = tdh.NewProc("TdhGetProperty") 22 | tdhGetPropertyOffsetAndSize = tdh.NewProc("TdhGetPropertyOffsetAndSize") 23 | tdhGetPropertySize = tdh.NewProc("TdhGetPropertySize") 24 | tdhLoadManifest = tdh.NewProc("TdhLoadManifest") 25 | tdhQueryProviderFieldInformation = tdh.NewProc("TdhQueryProviderFieldInformation") 26 | tdhQueryRemoteWBEMProviderFieldInformation = tdh.NewProc("TdhQueryRemoteWBEMProviderFieldInformation") 27 | tdhUnloadManifest = tdh.NewProc("TdhUnloadManifest") 28 | ) 29 | -------------------------------------------------------------------------------- /etw/tdh_headers.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "fmt" 8 | "strings" 9 | "unsafe" 10 | ) 11 | 12 | /* 13 | typedef struct _TDH_CONTEXT { 14 | ULONGLONG ParameterValue; 15 | TDH_CONTEXT_TYPE ParameterType; 16 | ULONG ParameterSize; 17 | } TDH_CONTEXT; 18 | */ 19 | 20 | type TdhContext struct { 21 | ParameterValue uint32 22 | ParameterType TdhContextType 23 | ParameterSize uint32 24 | } 25 | 26 | /* 27 | typedef enum _TDH_CONTEXT_TYPE { 28 | TDH_CONTEXT_WPP_TMFFILE, 29 | TDH_CONTEXT_WPP_TMFSEARCHPATH, 30 | TDH_CONTEXT_WPP_GMT, 31 | TDH_CONTEXT_POINTERSIZE, 32 | TDH_CONTEXT_PDB_PATH, 33 | TDH_CONTEXT_MAXIMUM 34 | } TDH_CONTEXT_TYPE; 35 | */ 36 | 37 | type TdhContextType int32 38 | 39 | const ( 40 | TDH_CONTEXT_WPP_TMFFILE = TdhContextType(0) 41 | TDH_CONTEXT_WPP_TMFSEARCHPATH = TdhContextType(1) 42 | TDH_CONTEXT_WPP_GMT = TdhContextType(2) 43 | TDH_CONTEXT_POINTERSIZE = TdhContextType(3) 44 | TDH_CONTEXT_MAXIMUM = TdhContextType(4) 45 | ) 46 | 47 | /* 48 | typedef struct _PROPERTY_DATA_DESCRIPTOR { 49 | ULONGLONG PropertyName; 50 | ULONG ArrayIndex; 51 | ULONG Reserved; 52 | } PROPERTY_DATA_DESCRIPTOR; 53 | */ 54 | 55 | type PropertyDataDescriptor struct { 56 | PropertyName uint64 57 | ArrayIndex uint32 58 | Reserved uint32 59 | } 60 | 61 | /* 62 | typedef struct _PROVIDER_FIELD_INFOARRAY { 63 | ULONG NumberOfElements; 64 | EVENT_FIELD_TYPE FieldType; 65 | PROVIDER_FIELD_INFO FieldInfoArray[ANYSIZE_ARRAY]; 66 | } PROVIDER_FIELD_INFOARRAY; 67 | */ 68 | 69 | type ProviderFieldInfoArray struct { 70 | NumberOfElements uint32 71 | FieldType EventFieldType // This field is initially an enum so I guess it has the size of an int 72 | FieldInfoArray [1]ProviderFieldInfo 73 | } 74 | 75 | /* 76 | typedef struct _PROVIDER_FIELD_INFO { 77 | ULONG NameOffset; 78 | ULONG DescriptionOffset; 79 | ULONGLONG Value; 80 | } PROVIDER_FIELD_INFO; 81 | */ 82 | type ProviderFieldInfo struct { 83 | NameOffset uint32 84 | DescriptionOffset uint32 85 | Value uint64 86 | } 87 | 88 | /* 89 | typedef enum _EVENT_FIELD_TYPE { 90 | EventKeywordInformation = 0, 91 | EventLevelInformation = 1, 92 | EventChannelInformation = 2, 93 | EventTaskInformation = 3, 94 | EventOpcodeInformation = 4, 95 | EventInformationMax = 5 96 | } EVENT_FIELD_TYPE; 97 | */ 98 | 99 | type EventFieldType int32 100 | 101 | const ( 102 | EventKeywordInformation = EventFieldType(0) 103 | EventLevelInformation = EventFieldType(1) 104 | EventChannelInformation = EventFieldType(2) 105 | EventTaskInformation = EventFieldType(3) 106 | EventOpcodeInformation = EventFieldType(4) 107 | EventInformationMax = EventFieldType(5) 108 | ) 109 | 110 | /* 111 | typedef struct _PROVIDER_ENUMERATION_INFO { 112 | ULONG NumberOfProviders; 113 | ULONG Reserved; 114 | TRACE_PROVIDER_INFO TraceProviderInfoArray[ANYSIZE_ARRAY]; 115 | } PROVIDER_ENUMERATION_INFO; 116 | */ 117 | 118 | type ProviderEnumerationInfo struct { 119 | NumberOfProviders uint32 120 | Reserved uint32 121 | TraceProviderInfoArray [1]TraceProviderInfo 122 | } 123 | 124 | /* 125 | typedef struct _TRACE_PROVIDER_INFO { 126 | GUID ProviderGuid; 127 | ULONG SchemaSource; 128 | ULONG ProviderNameOffset; 129 | } TRACE_PROVIDER_INFO; 130 | */ 131 | 132 | type TraceProviderInfo struct { 133 | ProviderGuid GUID 134 | SchemaSource uint32 135 | ProviderNameOffset uint32 136 | } 137 | 138 | /* 139 | typedef struct _TRACE_EVENT_INFO { 140 | GUID ProviderGuid; 141 | GUID EventGuid; 142 | EVENT_DESCRIPTOR EventDescriptor; 143 | DECODING_SOURCE DecodingSource; 144 | ULONG ProviderNameOffset; 145 | ULONG LevelNameOffset; 146 | ULONG ChannelNameOffset; 147 | ULONG KeywordsNameOffset; 148 | ULONG TaskNameOffset; 149 | ULONG OpcodeNameOffset; 150 | ULONG EventMessageOffset; 151 | ULONG ProviderMessageOffset; 152 | ULONG BinaryXMLOffset; 153 | ULONG BinaryXMLSize; 154 | union { 155 | ULONG EventNameOffset; 156 | ULONG ActivityIDNameOffset; 157 | }; 158 | union { 159 | ULONG EventAttributesOffset; 160 | ULONG RelatedActivityIDNameOffset; 161 | }; 162 | ULONG PropertyCount; 163 | ULONG TopLevelPropertyCount; 164 | union { 165 | TEMPLATE_FLAGS Flags; 166 | struct { 167 | ULONG Reserved : 4; 168 | ULONG Tags : 28; 169 | }; 170 | }; 171 | EVENT_PROPERTY_INFO EventPropertyInfoArray[ANYSIZE_ARRAY]; 172 | } TRACE_EVENT_INFO; 173 | 174 | typedef struct _TRACE_EVENT_INFO { 175 | GUID ProviderGuid; 176 | GUID EventGuid; 177 | EVENT_DESCRIPTOR EventDescriptor; 178 | DECODING_SOURCE DecodingSource; 179 | ULONG ProviderNameOffset; 180 | ULONG LevelNameOffset; 181 | ULONG ChannelNameOffset; 182 | ULONG KeywordsNameOffset; 183 | ULONG TaskNameOffset; 184 | ULONG OpcodeNameOffset; 185 | ULONG EventMessageOffset; 186 | ULONG ProviderMessageOffset; 187 | ULONG BinaryXMLOffset; 188 | ULONG BinaryXMLSize; 189 | ULONG ActivityIDNameOffset; 190 | ULONG RelatedActivityIDNameOffset; 191 | ULONG PropertyCount; 192 | ULONG TopLevelPropertyCount; 193 | TEMPLATE_FLAGS Flags; 194 | EVENT_PROPERTY_INFO EventPropertyInfoArray[ANYSIZE_ARRAY]; 195 | } TRACE_EVENT_INFO, *PTRACE_EVENT_INFO; 196 | 197 | */ 198 | 199 | type TraceEventInfo struct { 200 | ProviderGUID GUID 201 | EventGUID GUID 202 | EventDescriptor EventDescriptor 203 | DecodingSource DecodingSource 204 | ProviderNameOffset uint32 205 | LevelNameOffset uint32 206 | ChannelNameOffset uint32 207 | KeywordsNameOffset uint32 208 | TaskNameOffset uint32 209 | OpcodeNameOffset uint32 210 | EventMessageOffset uint32 211 | ProviderMessageOffset uint32 212 | BinaryXMLOffset uint32 213 | BinaryXMLSize uint32 214 | ActivityIDNameOffset uint32 215 | RelatedActivityIDNameOffset uint32 216 | PropertyCount uint32 217 | TopLevelPropertyCount uint32 218 | Flags TemplateFlags 219 | EventPropertyInfoArray [1]EventPropertyInfo 220 | } 221 | 222 | func (t *TraceEventInfo) pointer() uintptr { 223 | return uintptr(unsafe.Pointer(t)) 224 | } 225 | 226 | func (t *TraceEventInfo) pointerOffset(offset uintptr) uintptr { 227 | return t.pointer() + offset 228 | } 229 | 230 | func (t *TraceEventInfo) stringAt(offset uintptr) string { 231 | if offset > 0 { 232 | return UTF16AtOffsetToString(t.pointer(), offset) 233 | } 234 | return "" 235 | } 236 | 237 | func (t *TraceEventInfo) cleanStringAt(offset uintptr) string { 238 | if offset > 0 { 239 | return strings.Trim(t.stringAt(offset), " ") 240 | } 241 | return "" 242 | } 243 | 244 | // Seems to be always empty 245 | func (t *TraceEventInfo) EventMessage() string { 246 | return t.cleanStringAt(uintptr(t.EventMessageOffset)) 247 | } 248 | 249 | func (t *TraceEventInfo) ProviderName() string { 250 | return t.cleanStringAt(uintptr(t.ProviderNameOffset)) 251 | } 252 | 253 | func (t *TraceEventInfo) TaskName() string { 254 | return t.cleanStringAt(uintptr(t.TaskNameOffset)) 255 | } 256 | 257 | func (t *TraceEventInfo) LevelName() string { 258 | return t.cleanStringAt(uintptr(t.LevelNameOffset)) 259 | } 260 | 261 | func (t *TraceEventInfo) OpcodeName() string { 262 | return t.cleanStringAt(uintptr(t.OpcodeNameOffset)) 263 | } 264 | 265 | func (t *TraceEventInfo) KeywordName() string { 266 | return t.cleanStringAt(uintptr(t.KeywordsNameOffset)) 267 | } 268 | 269 | func (t *TraceEventInfo) ChannelName() string { 270 | return t.cleanStringAt(uintptr(t.ChannelNameOffset)) 271 | } 272 | 273 | // Seems to be always empty 274 | func (t *TraceEventInfo) ActivityIDName() string { 275 | return t.stringAt(uintptr(t.ActivityIDNameOffset)) 276 | } 277 | 278 | // Seems to be always empty 279 | func (t *TraceEventInfo) RelatedActivityIDName() string { 280 | return t.stringAt(uintptr(t.RelatedActivityIDNameOffset)) 281 | } 282 | 283 | func (t *TraceEventInfo) IsMof() bool { 284 | return t.DecodingSource == DecodingSourceWbem 285 | } 286 | 287 | func (t *TraceEventInfo) IsXML() bool { 288 | return t.DecodingSource == DecodingSourceXMLFile 289 | } 290 | 291 | func (t *TraceEventInfo) EventID() uint16 { 292 | if t.IsXML() { 293 | return t.EventDescriptor.Id 294 | } else if t.IsMof() { 295 | if c, ok := MofClassMapping[t.EventGUID.Data1]; ok { 296 | return c.BaseId + uint16(t.EventDescriptor.Opcode) 297 | } 298 | } 299 | // not meaningful, cannot be used to identify event 300 | return 0 301 | } 302 | 303 | func (t *TraceEventInfo) GetEventPropertyInfoAt(i uint32) *EventPropertyInfo { 304 | if i < t.PropertyCount { 305 | pEpi := uintptr(unsafe.Pointer(&t.EventPropertyInfoArray[0])) 306 | pEpi += uintptr(i) * unsafe.Sizeof(EventPropertyInfo{}) 307 | // this line triggers checkptr 308 | // I guess that is because TraceInfo is variable size C 309 | // struct we had to hack with to make it compatible with Go 310 | return ((*EventPropertyInfo)(unsafe.Pointer(pEpi))) 311 | } 312 | panic(fmt.Errorf("index out of range")) 313 | } 314 | 315 | func (t *TraceEventInfo) PropertyNameOffset(i uint32) uintptr { 316 | return t.pointer() + uintptr(t.GetEventPropertyInfoAt(i).NameOffset) 317 | } 318 | 319 | /* 320 | typedef enum _DECODING_SOURCE { 321 | DecodingSourceXMLFile = 0, 322 | DecodingSourceWbem = 1, 323 | DecodingSourceWPP = 2 324 | } DECODING_SOURCE; 325 | */ 326 | 327 | type DecodingSource int32 328 | 329 | const ( 330 | DecodingSourceXMLFile = DecodingSource(0) 331 | DecodingSourceWbem = DecodingSource(1) 332 | DecodingSourceWPP = DecodingSource(2) 333 | ) 334 | 335 | /* 336 | typedef enum _TEMPLATE_FLAGS { 337 | TEMPLATE_EVENT_DATA = 1, 338 | TEMPLATE_USER_DATA = 2 339 | } TEMPLATE_FLAGS; 340 | */ 341 | 342 | type TemplateFlags int32 343 | 344 | const ( 345 | TEMPLATE_EVENT_DATA = TemplateFlags(1) 346 | TEMPLATE_USER_DATA = TemplateFlags(2) 347 | ) 348 | 349 | /* 350 | typedef struct _EVENT_MAP_INFO { 351 | ULONG NameOffset; 352 | MAP_FLAGS Flag; 353 | ULONG EntryCount; 354 | union { 355 | MAP_VALUETYPE MapEntryValueType; 356 | ULONG FormatStringOffset; 357 | }; 358 | EVENT_MAP_ENTRY MapEntryArray[ANYSIZE_ARRAY]; 359 | } EVENT_MAP_INFO; 360 | */ 361 | 362 | type EventMapInfo struct { 363 | NameOffset uint32 364 | Flag MapFlags 365 | EntryCount uint32 366 | Union uint32 // Not sure about size of union depends on size of enum MAP_VALUETYPE 367 | MapEntryArray [1]EventMapEntry 368 | } 369 | 370 | func (e *EventMapInfo) GetEventMapEntryAt(i int) *EventMapEntry { 371 | if uint32(i) < e.EntryCount { 372 | pEmi := uintptr(unsafe.Pointer(&e.MapEntryArray[0])) 373 | pEmi += uintptr(i) * unsafe.Sizeof(EventMapEntry{}) 374 | return ((*EventMapEntry)(unsafe.Pointer(pEmi))) 375 | } 376 | panic(fmt.Errorf("Index out of range")) 377 | } 378 | 379 | /* 380 | // The mapped string values defined in a manifest will contain a trailing space 381 | // in the EVENT_MAP_ENTRY structure. Replace the trailing space with a null- 382 | // terminating character, so that the bit mapped strings are correctly formatted. 383 | 384 | void RemoveTrailingSpace(PEVENT_MAP_INFO pMapInfo) 385 | { 386 | SIZE_T ByteLength = 0; 387 | 388 | for (DWORD i = 0; i < pMapInfo->EntryCount; i++) 389 | { 390 | ByteLength = (wcslen((LPWSTR)((PBYTE)pMapInfo + pMapInfo->MapEntryArray[i].OutputOffset)) - 1) * 2; 391 | *((LPWSTR)((PBYTE)pMapInfo + (pMapInfo->MapEntryArray[i].OutputOffset + ByteLength))) = L'\0'; 392 | } 393 | } 394 | */ 395 | 396 | func (e *EventMapInfo) RemoveTrailingSpace() { 397 | for i := uint32(0); i < e.EntryCount; i++ { 398 | me := e.GetEventMapEntryAt(int(i)) 399 | pStr := uintptr(unsafe.Pointer(e)) + uintptr(me.OutputOffset) 400 | byteLen := (Wcslen(((*uint16)(unsafe.Pointer(pStr)))) - 1) * 2 401 | *((*uint16)(unsafe.Pointer(pStr + uintptr(byteLen)))) = 0 402 | } 403 | } 404 | 405 | /* 406 | typedef enum _MAP_FLAGS { 407 | EVENTMAP_INFO_FLAG_MANIFEST_VALUEMAP = 1, 408 | EVENTMAP_INFO_FLAG_MANIFEST_BITMAP = 2, 409 | EVENTMAP_INFO_FLAG_MANIFEST_PATTERNMAP = 4, 410 | EVENTMAP_INFO_FLAG_WBEM_VALUEMAP = 8, 411 | EVENTMAP_INFO_FLAG_WBEM_BITMAP = 16, 412 | EVENTMAP_INFO_FLAG_WBEM_FLAG = 32, 413 | EVENTMAP_INFO_FLAG_WBEM_NO_MAP = 64 414 | } MAP_FLAGS; 415 | */ 416 | 417 | type MapFlags int32 418 | 419 | const ( 420 | EVENTMAP_INFO_FLAG_MANIFEST_VALUEMAP = MapFlags(1) 421 | EVENTMAP_INFO_FLAG_MANIFEST_BITMAP = MapFlags(2) 422 | EVENTMAP_INFO_FLAG_MANIFEST_PATTERNMAP = MapFlags(4) 423 | EVENTMAP_INFO_FLAG_WBEM_VALUEMAP = MapFlags(8) 424 | EVENTMAP_INFO_FLAG_WBEM_BITMAP = MapFlags(16) 425 | EVENTMAP_INFO_FLAG_WBEM_FLAG = MapFlags(32) 426 | EVENTMAP_INFO_FLAG_WBEM_NO_MAP = MapFlags(64) 427 | ) 428 | 429 | /* 430 | typedef enum _MAP_VALUETYPE 431 | { 432 | EVENTMAP_ENTRY_VALUETYPE_ULONG = 0, 433 | EVENTMAP_ENTRY_VALUETYPE_STRING = 1 434 | } MAP_VALUETYPE; 435 | */ 436 | 437 | type MapValueType int32 438 | 439 | const ( 440 | EVENTMAP_ENTRY_VALUETYPE_ULONG = MapValueType(0) 441 | EVENTMAP_ENTRY_VALUETYPE_STRING = MapValueType(1) 442 | ) 443 | 444 | /* 445 | typedef struct _EVENT_MAP_ENTRY { 446 | ULONG OutputOffset; 447 | __C89_NAMELESS union { 448 | ULONG Value; 449 | ULONG InputOffset; 450 | }; 451 | } EVENT_MAP_ENTRY, *PEVENT_MAP_ENTRY; 452 | */ 453 | 454 | type EventMapEntry struct { 455 | OutputOffset uint32 456 | Union uint32 457 | } 458 | 459 | type PropertyFlags int32 460 | 461 | const ( 462 | PropertyStruct = PropertyFlags(0x1) 463 | PropertyParamLength = PropertyFlags(0x2) 464 | PropertyParamCount = PropertyFlags(0x4) 465 | PropertyWBEMXmlFragment = PropertyFlags(0x8) 466 | PropertyParamFixedLength = PropertyFlags(0x10) 467 | ) 468 | 469 | /* 470 | typedef struct _EVENT_PROPERTY_INFO { 471 | PROPERTY_FLAGS Flags; 472 | ULONG NameOffset; 473 | union { 474 | struct { 475 | USHORT InType; 476 | USHORT OutType; 477 | ULONG MapNameOffset; 478 | } nonStructType; 479 | struct { 480 | USHORT StructStartIndex; 481 | USHORT NumOfStructMembers; 482 | ULONG padding; 483 | } structType; 484 | struct { 485 | USHORT InType; 486 | USHORT OutType; 487 | ULONG CustomSchemaOffset; 488 | } customSchemaType; 489 | }; 490 | union { 491 | USHORT count; 492 | USHORT countPropertyIndex; 493 | }; 494 | union { 495 | USHORT length; 496 | USHORT lengthPropertyIndex; 497 | }; 498 | union { 499 | ULONG Reserved; 500 | struct { 501 | ULONG Tags : 28; 502 | }; 503 | }; 504 | } EVENT_PROPERTY_INFO; 505 | */ 506 | 507 | type EventPropertyInfo struct { 508 | Flags PropertyFlags 509 | NameOffset uint32 510 | TypeUnion struct { 511 | u1 uint16 512 | u2 uint16 513 | u3 uint32 514 | } 515 | CountUnion uint16 516 | LengthUnion uint16 517 | ResTagUnion uint32 518 | } 519 | 520 | func (i *EventPropertyInfo) InType() uint16 { 521 | return i.TypeUnion.u1 522 | } 523 | func (i *EventPropertyInfo) StructStartIndex() uint16 { 524 | return i.InType() 525 | } 526 | 527 | func (i *EventPropertyInfo) OutType() uint16 { 528 | return i.TypeUnion.u2 529 | } 530 | 531 | func (i *EventPropertyInfo) NumOfStructMembers() uint16 { 532 | return i.OutType() 533 | } 534 | 535 | func (i *EventPropertyInfo) MapNameOffset() uint32 { 536 | return i.CustomSchemaOffset() 537 | } 538 | 539 | func (i *EventPropertyInfo) CustomSchemaOffset() uint32 { 540 | return i.TypeUnion.u3 541 | } 542 | 543 | func (i *EventPropertyInfo) Count() uint16 { 544 | return i.CountUnion 545 | } 546 | 547 | func (i *EventPropertyInfo) CountPropertyIndex() uint16 { 548 | return i.CountUnion 549 | } 550 | 551 | func (i *EventPropertyInfo) LengthPropertyIndex() uint16 { 552 | return i.LengthUnion 553 | } 554 | 555 | func (i *EventPropertyInfo) Length() uint16 { 556 | return i.LengthUnion 557 | } 558 | 559 | type TdhInType uint32 560 | 561 | // found info there: https://github.com/microsoft/ETW2JSON/blob/6721e0438733b316d316d36c488166853a05f836/Deserializer/Tdh.cs 562 | const ( 563 | TdhInTypeNull = TdhInType(iota) 564 | TdhInTypeUnicodestring 565 | TdhInTypeAnsistring 566 | TdhInTypeInt8 567 | TdhInTypeUint8 568 | TdhInTypeInt16 569 | TdhInTypeUint16 570 | TdhInTypeInt32 571 | TdhInTypeUint32 572 | TdhInTypeInt64 573 | TdhInTypeUint64 574 | TdhInTypeFloat 575 | TdhInTypeDouble 576 | TdhInTypeBoolean 577 | TdhInTypeBinary 578 | TdhInTypeGUID 579 | TdhInTypePointer 580 | TdhInTypeFiletime 581 | TdhInTypeSystemtime 582 | TdhInTypeSid 583 | TdhInTypeHexint32 584 | TdhInTypeHexint64 // End of winmeta types 585 | ) 586 | 587 | const ( 588 | TdhInTypeCountedstring = TdhInType(iota + 300) // Start of TDH intypes for WBEM. 589 | TdhInTypeCountedansistring 590 | TdhInTypeReversedcountedstring 591 | TdhInTypeReversedcountedansistring 592 | TdhInTypeNonnullterminatedstring 593 | TdhInTypeNonnullterminatedansistring 594 | TdhInTypeUnicodechar 595 | TdhInTypeAnsichar 596 | TdhInTypeSizet 597 | TdhInTypeHexdump 598 | TdhInTypeWbemsid 599 | ) 600 | 601 | type TdhOutType uint32 602 | 603 | const ( 604 | TdhOutTypeNull = TdhOutType(iota) 605 | TdhOutTypeString 606 | TdhOutTypeDatetime 607 | TdhOutTypeByte 608 | TdhOutTypeUnsignedbyte 609 | TdhOutTypeShort 610 | TdhOutTypeUnsignedshort 611 | TdhOutTypeInt 612 | TdhOutTypeUnsignedint 613 | TdhOutTypeLong 614 | TdhOutTypeUnsignedlong 615 | TdhOutTypeFloat 616 | TdhOutTypeDouble 617 | TdhOutTypeBoolean 618 | TdhOutTypeGUID 619 | TdhOutTypeHexbinary 620 | TdhOutTypeHexint8 621 | TdhOutTypeHexint16 622 | TdhOutTypeHexint32 623 | TdhOutTypeHexint64 624 | TdhOutTypePid 625 | TdhOutTypeTid 626 | TdhOutTypePort 627 | TdhOutTypeIpv4 628 | TdhOutTypeIpv6 629 | TdhOutTypeSocketaddress 630 | TdhOutTypeCimdatetime 631 | TdhOutTypeEtwtime 632 | TdhOutTypeXML 633 | TdhOutTypeErrorcode 634 | TdhOutTypeWin32error 635 | TdhOutTypeNtstatus 636 | TdhOutTypeHresult // End of winmeta outtypes. 637 | TdhOutTypeCultureInsensitiveDatetime // Culture neutral datetime string. 638 | TdhOutTypeJSON 639 | ) 640 | 641 | const ( 642 | // Start of TDH outtypes for WBEM. 643 | TdhOutTypeREDUCEDSTRING = TdhOutType(iota + 300) 644 | TdhOutTypeNOPRINT 645 | ) 646 | -------------------------------------------------------------------------------- /etw/utils.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "crypto/rand" 8 | "fmt" 9 | "syscall" 10 | "unsafe" 11 | ) 12 | 13 | func max(a, b int) int { 14 | if a < b { 15 | return b 16 | } 17 | return a 18 | } 19 | 20 | // UTF16BytesToString transforms a bytes array of UTF16 encoded characters to 21 | // a Go string 22 | func UTF16BytesToString(utf16 []byte) string { 23 | return syscall.UTF16ToString(*(*[]uint16)(unsafe.Pointer(&utf16))) 24 | } 25 | 26 | // UTF16PtrToString transforms a *uint16 to a Go string 27 | func UTF16PtrToString(utf16 *uint16) string { 28 | return UTF16AtOffsetToString(uintptr(unsafe.Pointer(utf16)), 0) 29 | } 30 | 31 | func Wcslen(uintf16 *uint16) (len uint64) { 32 | for it := uintptr((unsafe.Pointer(uintf16))); ; it += 2 { 33 | wc := (*uint16)(unsafe.Pointer(it)) 34 | if *wc == 0 { 35 | return 36 | } 37 | len++ 38 | } 39 | } 40 | 41 | func UTF16AtOffsetToString(pstruct uintptr, offset uintptr) string { 42 | out := make([]uint16, 0, 64) 43 | wc := (*uint16)(unsafe.Pointer(pstruct + offset)) 44 | for i := uintptr(2); *wc != 0; i += 2 { 45 | out = append(out, *wc) 46 | wc = (*uint16)(unsafe.Pointer(pstruct + offset + i)) 47 | } 48 | return syscall.UTF16ToString(out) 49 | } 50 | 51 | func CopyData(pointer uintptr, size int) []byte { 52 | out := make([]byte, 0, size) 53 | for it := pointer; it != pointer+uintptr(size); it++ { 54 | b := (*byte)(unsafe.Pointer(it)) 55 | out = append(out, *b) 56 | } 57 | return out 58 | } 59 | 60 | // UUID is a simple UUIDgenerator 61 | func UUID() (uuid string, err error) { 62 | b := make([]byte, 16) 63 | _, err = rand.Read(b) 64 | if err != nil { 65 | return 66 | } 67 | uuid = fmt.Sprintf("%X-%X-%X-%X-%X", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) 68 | return 69 | } 70 | -------------------------------------------------------------------------------- /etw/utils_test.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package etw 5 | 6 | import ( 7 | "syscall" 8 | "testing" 9 | "unsafe" 10 | 11 | "github.com/0xrawsec/toast" 12 | ) 13 | 14 | func TestUtils(t *testing.T) { 15 | 16 | tt := toast.FromT(t) 17 | 18 | s := "this is a utf16 string" 19 | sutf16, err := syscall.UTF16PtrFromString(s) 20 | tt.CheckErr(err) 21 | 22 | tt.Assert(UTF16PtrToString(sutf16) == s) 23 | tt.Assert(Wcslen(sutf16) == uint64(len(s))) 24 | 25 | // we have to double the length because we are in utf16 26 | butf16 := CopyData(uintptr(unsafe.Pointer(sutf16)), len(s)*2) 27 | 28 | tt.Assert(len(butf16) == len(s)*2) 29 | tt.Assert(UTF16BytesToString(butf16) == s) 30 | 31 | uuid, err := UUID() 32 | tt.CheckErr(err) 33 | t.Log(uuid) 34 | } 35 | -------------------------------------------------------------------------------- /examples/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | export GOOS=windows 5 | 6 | for example in `find $(dirname $(realpath $0)) -type f -name '*.go'` 7 | do 8 | echo -e "Running example: $(basename $example)\n" 9 | go run $example 10 | done -------------------------------------------------------------------------------- /examples/simple/simple.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "encoding/json" 9 | "fmt" 10 | "time" 11 | 12 | "github.com/0xrawsec/golang-etw/etw" 13 | ) 14 | 15 | func main() { 16 | // ETW needs a trace to be created before being able to consume from 17 | // it. Traces can be created using golang-etw or they might be already 18 | // existing (created from an autologgers for instance) like Eventlog-Security. 19 | 20 | // Creating the trace (producer part) 21 | s := etw.NewRealTimeSession("TestingGoEtw") 22 | 23 | // We have to stop the session or it will be kept alive and session name 24 | // will not be available anymore for next calls 25 | defer s.Stop() 26 | 27 | // we need to enable the trace to collect logs from given providers 28 | // several providers can be enabled per trace, in this example we 29 | // enable only one provider 30 | if err := s.EnableProvider(etw.MustParseProvider("Microsoft-Windows-Kernel-File")); err != nil { 31 | panic(err) 32 | } 33 | 34 | // Consuming from the trace 35 | c := etw.NewRealTimeConsumer(context.Background()) 36 | 37 | defer c.Stop() 38 | 39 | c.FromSessions(s) 40 | 41 | // When events are parsed they get sent to Consumer's 42 | // Events channel by the default EventCallback method 43 | // EventCallback can be modified to do otherwise 44 | go func() { 45 | var b []byte 46 | var err error 47 | for e := range c.Events { 48 | if b, err = json.Marshal(e); err != nil { 49 | panic(err) 50 | } 51 | fmt.Println(string(b)) 52 | } 53 | }() 54 | 55 | if err := c.Start(); err != nil { 56 | panic(err) 57 | } 58 | 59 | time.Sleep(5 * time.Second) 60 | 61 | if c.Err() != nil { 62 | panic(c.Err()) 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/0xrawsec/golang-etw 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/0xrawsec/golang-utils v1.3.1 7 | github.com/0xrawsec/toast v1.2.3 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/0xrawsec/golang-utils v1.3.1 h1:jjiBzsxzcQPkmEV5KONJY4OnCoqTTW1eQMJcpSdk3hw= 2 | github.com/0xrawsec/golang-utils v1.3.1/go.mod h1:DADTtCFY10qXjWmUVhhJqQIZdSweaHH4soYUDEi8mj0= 3 | github.com/0xrawsec/toast v1.2.3 h1:nTs5NyAdmSoDfxlYjMVMYb9wj3C/MFpnoIoQBPUsHXg= 4 | github.com/0xrawsec/toast v1.2.3/go.mod h1:sRvfNYxqVoH1sZnE18s9Knm/lkbarTGNvaNVBf2/h1k= 5 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 6 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 7 | github.com/pkg/sftp v1.10.0/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk= 8 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 9 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 10 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 11 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 12 | golang.org/x/tools v0.0.0-20190320215829-36c10c0a621f/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 13 | -------------------------------------------------------------------------------- /tools/etwdump/etwdump.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "math/rand" 9 | "os" 10 | "os/exec" 11 | "os/signal" 12 | "path/filepath" 13 | "regexp" 14 | "sort" 15 | "strconv" 16 | "strings" 17 | "sync" 18 | "time" 19 | 20 | "github.com/0xrawsec/golang-etw/etw" 21 | "github.com/0xrawsec/golang-utils/log" 22 | ) 23 | 24 | const ( 25 | copyright = "etwdump Copyright (C) 2022 RawSec SARL (@0xrawsec)" 26 | license = `GPLv3: This program comes with ABSOLUTELY NO WARRANTY.` 27 | ) 28 | 29 | func clear() { 30 | cmd := exec.Command("cmd", "/c", "cls") 31 | cmd.Stdout = os.Stdout 32 | cmd.Run() 33 | } 34 | 35 | type EventWrapper struct { 36 | Event *etw.Event 37 | } 38 | 39 | func getAccessString(guid string) (s string) { 40 | var err error 41 | 42 | if s, err = etw.GetAccessString(guid); err != nil { 43 | panic(err) 44 | } 45 | 46 | return 47 | } 48 | 49 | func setAccess(guid string) { 50 | var sid *etw.SID 51 | var err error 52 | 53 | if sid, err = etw.ConvertStringSidToSidW("S-1-5-18"); err != nil { 54 | log.Errorf("Failed to convert string to sid%s", err) 55 | return 56 | } 57 | g := etw.MustParseGUIDFromString(guid) 58 | 59 | if err = etw.EventAccessControl(g, 60 | uint32(etw.EVENT_SECURITY_SET_DACL), 61 | sid, 62 | 0x120fff, 63 | true, 64 | ); err != nil { 65 | log.Errorf("Failed to set access%s", err) 66 | } 67 | } 68 | 69 | func parseFilter(s string) (provider string, eventIds []uint16) { 70 | eventIds = make([]uint16, 0) 71 | split := strings.Split(s, ":") 72 | if len(split) == 2 { 73 | provider = split[0] 74 | for _, ss := range strings.Split(split[1], ",") { 75 | if eventId, err := strconv.ParseUint(ss, 10, 16); err == nil { 76 | eventIds = append(eventIds, uint16(eventId)) 77 | } 78 | } 79 | } 80 | return 81 | } 82 | 83 | func unsafeRandomGuid() *etw.GUID { 84 | // not safe as determininstic 85 | rand.Seed(time.Now().UnixNano()) 86 | return &etw.GUID{ 87 | Data1: rand.Uint32(), 88 | Data2: uint16(rand.Uint32()), 89 | Data3: uint16(rand.Uint32()), 90 | Data4: [8]byte{ 91 | uint8(rand.Uint32()), 92 | uint8(rand.Uint32()), 93 | uint8(rand.Uint32()), 94 | uint8(rand.Uint32()), 95 | uint8(rand.Uint32()), 96 | uint8(rand.Uint32()), 97 | uint8(rand.Uint32()), 98 | uint8(rand.Uint32()), 99 | }, 100 | } 101 | } 102 | 103 | func providerOrFail(s string) etw.Provider { 104 | if prov, err := etw.ParseProvider(s); err != nil { 105 | log.Abort(1, err) 106 | return prov 107 | } else { 108 | return prov 109 | } 110 | } 111 | 112 | type Stats struct { 113 | sync.RWMutex 114 | s map[string]map[uint16]uint64 115 | start time.Time 116 | update time.Time 117 | Count uint64 118 | } 119 | 120 | func NewStats() *Stats { 121 | return &Stats{ 122 | s: make(map[string]map[uint16]uint64), 123 | } 124 | } 125 | 126 | func (s *Stats) Update(e *etw.Event) { 127 | s.Lock() 128 | defer s.Unlock() 129 | 130 | var ok bool 131 | var eventIDs map[uint16]uint64 132 | 133 | now := time.Now() 134 | 135 | key := e.System.Channel 136 | if key == "" { 137 | key = e.System.Provider.Guid 138 | } 139 | 140 | if eventIDs, ok = s.s[key]; !ok { 141 | eventIDs = make(map[uint16]uint64) 142 | s.s[key] = eventIDs 143 | } 144 | 145 | eventIDs[e.System.EventID]++ 146 | s.Count++ 147 | 148 | if s.start.IsZero() { 149 | s.start = now 150 | } 151 | 152 | s.update = now 153 | } 154 | 155 | func (s *Stats) Show() { 156 | s.RLock() 157 | defer s.RUnlock() 158 | 159 | delta := float64(s.update.Unix() - s.start.Unix()) 160 | channels := make(sort.StringSlice, 0, len(s.s)) 161 | for c := range s.s { 162 | channels = append(channels, c) 163 | } 164 | sort.Sort(channels) 165 | for _, c := range channels { 166 | chanCount := uint64(0) 167 | ids := make(sort.IntSlice, 0, len(s.s[c])) 168 | for id, cnt := range s.s[c] { 169 | ids = append(ids, int(id)) 170 | chanCount += cnt 171 | } 172 | sort.Sort(ids) 173 | // Printing output 174 | chanEps := float64(chanCount) / delta 175 | fmt.Printf("%s: %d (%.2f EPS)\n", c, chanCount, chanEps) 176 | for _, id := range ids { 177 | count := s.s[c][uint16(id)] 178 | eps := float64(count) / delta 179 | fmt.Printf("\t%d: %d (%.2f EPS)\n", id, count, eps) 180 | } 181 | } 182 | globEps := float64(s.Count) / delta 183 | fmt.Printf("Global: %d (%.2f EPS)", s.Count, globEps) 184 | } 185 | 186 | func main() { 187 | var ( 188 | debug bool 189 | listKernelProviders bool 190 | listProviders bool 191 | access bool 192 | set bool 193 | noout bool 194 | fstats bool 195 | filemon bool 196 | attach string 197 | regex string 198 | outfile string 199 | autologger string 200 | cregex *regexp.Regexp 201 | kernelTraceFlags uint32 202 | 203 | producers []*etw.RealTimeSession 204 | 205 | sessionName = "EtwdumpTraceSession" 206 | sessions = make([]string, 0) 207 | writer = os.Stdout 208 | stats = NewStats() 209 | ) 210 | 211 | flag.StringVar(&sessionName, "s", sessionName, "ETW session name") 212 | flag.StringVar(&attach, "a", attach, "Attach to existing session(s) (comma separated)") 213 | flag.StringVar(®ex, "e", regex, "Regex to filter in events or providers when listed") 214 | flag.StringVar(&outfile, "o", outfile, "Output file") 215 | flag.StringVar(&autologger, "autologger", autologger, "Creates autologger and enables providers") 216 | flag.BoolVar(&access, "access", access, "List accesses to GUIDs") 217 | flag.BoolVar(&set, "set", set, "Set accesses to GUIDs") 218 | flag.BoolVar(&debug, "debug", debug, "Enable debug messages") 219 | flag.BoolVar(&listKernelProviders, "lk", listKernelProviders, "List kernel providers") 220 | flag.BoolVar(&listProviders, "lp", listProviders, "List providers") 221 | flag.BoolVar(&noout, "noout", noout, "Do not write logs") 222 | flag.BoolVar(&fstats, "stats", fstats, "Show statistics about events") 223 | flag.BoolVar(&filemon, "filemon", filemon, "Monitor file read/writes") 224 | flag.Usage = func() { 225 | fmt.Fprintf(os.Stderr, "%s\n%s\n", copyright, license) 226 | fmt.Fprintf(os.Stderr, "Version: %s (commit: %s)\n\n", version, commitID) 227 | fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] PROVIDERS...\n", filepath.Base(os.Args[0])) 228 | fmt.Fprintf(os.Stderr, "Options:\n") 229 | flag.PrintDefaults() 230 | os.Exit(0) 231 | } 232 | 233 | flag.Parse() 234 | 235 | if debug { 236 | log.SetLogLevel(log.LDebug) 237 | } 238 | 239 | providers := flag.Args() 240 | if filemon { 241 | if len(attach) == 0 { 242 | providers = []string{FilemonKernelProcessProvider, FilemonProvider} 243 | } 244 | } 245 | 246 | log.Debugf("PID: %d", os.Getpid()) 247 | 248 | // list kernel providers 249 | if listKernelProviders { 250 | fmt.Println("Kernel Providers") 251 | for _, pd := range etw.KernelProviders { 252 | fmt.Printf("\t%s: %s\n", pd.Name, pd.GUID) 253 | } 254 | os.Exit(0) 255 | } 256 | 257 | // build up regex 258 | if regex != "" { 259 | cregex = regexp.MustCompile(regex) 260 | } 261 | 262 | if listProviders { 263 | pmap := etw.EnumerateProviders() 264 | names := make([]string, 0, len(pmap)) 265 | maxLen := 0 266 | for name, prov := range pmap { 267 | // we don't want do display GUID keys 268 | if name == prov.GUID { 269 | continue 270 | } 271 | if cregex != nil { 272 | if !cregex.MatchString(name) { 273 | continue 274 | } 275 | } 276 | if len(name) > maxLen { 277 | maxLen = len(name) 278 | } 279 | names = append(names, name) 280 | } 281 | sort.Strings(names) 282 | for _, name := range names { 283 | fmt.Printf("%s%s %s\n", name, strings.Repeat(" ", maxLen-len(name)), pmap[name].GUID) 284 | } 285 | os.Exit(0) 286 | } 287 | 288 | if access { 289 | if set { 290 | for _, provider := range providers { 291 | setAccess(providerOrFail(provider).GUID) 292 | } 293 | } 294 | 295 | fmt.Println("Listing access rights") 296 | for _, provider := range providers { 297 | //fmt.Printf("%s: %s\n", provider, getAccessString(providerOrFail(provider).GUID)) 298 | fmt.Printf("%s: %s\n", provider, getAccessString(provider)) 299 | } 300 | os.Exit(0) 301 | } 302 | 303 | // opening output file if needed 304 | if outfile != "" { 305 | if fd, err := os.Create(outfile); err != nil { 306 | log.Errorf("Failed to open output file: %s", err) 307 | } else { 308 | writer = fd 309 | } 310 | } 311 | 312 | if autologger != "" { 313 | a := etw.AutoLogger{ 314 | Name: autologger, 315 | Guid: unsafeRandomGuid().String(), 316 | LogFileMode: 0x8001c0, 317 | BufferSize: 64, 318 | ClockType: 2, 319 | MaxFileSize: 200, 320 | } 321 | 322 | if !a.Exists() { 323 | a.Create() 324 | } 325 | 326 | for _, provider := range providers { 327 | var p etw.Provider 328 | var err error 329 | if etw.IsKernelProvider(provider) { 330 | continue 331 | 332 | } 333 | 334 | if p, err = etw.ParseProvider(provider); err != nil { 335 | log.Abort(1, err) 336 | } 337 | 338 | if err = a.EnableProvider(p); err != nil { 339 | log.Abort(1, fmt.Errorf("Failed to enable provider: %s", err)) 340 | } 341 | } 342 | 343 | os.Exit(0) 344 | } 345 | 346 | // We create a private producer 347 | p := etw.NewRealTimeSession(sessionName) 348 | 349 | // We process the providers provided in the command line 350 | for _, provStr := range providers { 351 | // this is a kernel provider 352 | if etw.IsKernelProvider(provStr) { 353 | log.Debugf("Enabling kernel provider: %s", provStr) 354 | kernelTraceFlags |= etw.GetKernelProviderFlags(provStr) 355 | } else { 356 | if prov, err := etw.ParseProvider(provStr); err != nil { 357 | log.Errorf("Failed to parse provider %s: %s", provStr, err) 358 | } else { 359 | log.Debugf("Enabling provider: %s", provStr) 360 | if err := p.EnableProvider(prov); err != nil { 361 | log.Errorf("Failed to enable provider %s: %s", provStr, err) 362 | } 363 | } 364 | } 365 | } 366 | 367 | // We enable producer only if it has at least a provider 368 | if len(p.Providers()) > 0 { 369 | producers = append(producers, p) 370 | } 371 | 372 | // We will start kernel producer only if necessary 373 | if kernelTraceFlags != 0 { 374 | kp := etw.NewKernelRealTimeSession(kernelTraceFlags) 375 | producers = append(producers, kp) 376 | } 377 | 378 | for _, p := range producers { 379 | log.Debugf("Starting producer: %s", p.TraceName()) 380 | if err := p.Start(); err != nil { 381 | panic(err) 382 | } 383 | sessions = append(sessions, p.TraceName()) 384 | } 385 | 386 | /** Consumer part **/ 387 | 388 | // additional sessions to trace (already started) 389 | if attach != "" { 390 | sessions = append(sessions, strings.Split(attach, ",")...) 391 | } 392 | 393 | c := etw.NewRealTimeConsumer(context.Background()). 394 | FromSessions(etw.SessionSlice(producers)...). 395 | FromTraceNames(sessions...) 396 | 397 | c.InitFilters(p.Providers()) 398 | 399 | if filemon { 400 | c.EventRecordCallback = filemonEventRecordCB 401 | 402 | c.PreparedCallback = filemonPreparedCB 403 | if cregex != nil { 404 | filemonRegex = cregex 405 | // don't use it to filter events 406 | cregex = nil 407 | } 408 | } 409 | 410 | if err := c.Start(); err != nil { 411 | log.Abort(1, "Failed to start traces: %s", err) 412 | } 413 | 414 | // Signal handler to catch interrupt 415 | h := make(chan os.Signal, 1) 416 | interrupt := sync.WaitGroup{} 417 | signal.Notify(h, os.Interrupt) 418 | 419 | interrupt.Add(1) 420 | go func() { 421 | defer interrupt.Done() 422 | 423 | <-h 424 | log.Infof("Received signal Interrupt") 425 | 426 | // we need to stop consumer first otherwise we trigger 427 | // some windows exception (probably due to access to freed memory) 428 | log.Debug("Stopping consumer") 429 | if err := c.Stop(); err != nil { 430 | log.Errorf("Error while stopping consumer: %s", err) 431 | } 432 | 433 | log.Infof("Skipped: %d", c.Skipped) 434 | 435 | log.Debug("Stopping producers") 436 | for _, p := range producers { 437 | if err := p.Stop(); err != nil { 438 | log.Errorf("Failed to stop producer: %s", err) 439 | } 440 | } 441 | 442 | }() 443 | 444 | go func() { 445 | log.Debug("Consuming events") 446 | for e := range c.Events { 447 | if fstats { 448 | stats.Update(e) 449 | if stats.Count%200 == 0 { 450 | clear() 451 | stats.Show() 452 | } 453 | // we write to output if needed 454 | if outfile != "" { 455 | if b, err := json.Marshal(EventWrapper{e}); err != nil { 456 | panic(err) 457 | } else { 458 | fmt.Fprintf(writer, "%s\n", string(b)) 459 | } 460 | } 461 | continue 462 | } 463 | 464 | if b, err := json.Marshal(EventWrapper{e}); err != nil { 465 | panic(err) 466 | } else { 467 | if cregex != nil { 468 | if cregex.Match(b) { 469 | if !noout { 470 | fmt.Fprintf(writer, "%s\n", string(b)) 471 | } 472 | } 473 | } else { 474 | if !noout { 475 | fmt.Fprintf(writer, "%s\n", string(b)) 476 | } 477 | } 478 | } 479 | } 480 | }() 481 | 482 | c.Wait() 483 | interrupt.Wait() 484 | } 485 | -------------------------------------------------------------------------------- /tools/etwdump/filemon.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/md5" 5 | "crypto/sha1" 6 | "crypto/sha256" 7 | "encoding/hex" 8 | "fmt" 9 | "io" 10 | "os" 11 | "regexp" 12 | 13 | "github.com/0xrawsec/golang-etw/etw" 14 | ) 15 | 16 | type file struct { 17 | name string 18 | flags struct { 19 | read bool 20 | write bool 21 | } 22 | } 23 | 24 | type process struct { 25 | pid uint32 26 | image string 27 | } 28 | 29 | type hashes struct { 30 | Md5 string 31 | Sha1 string 32 | Sha256 string 33 | Sha512 string 34 | Error string 35 | } 36 | 37 | func (h *hashes) String() string { 38 | if h.Error != "" { 39 | return fmt.Sprintf("MD5=?;SHA1=?;SHA256=?;SHA512=?;ERROR=%s", h.Error) 40 | } 41 | return fmt.Sprintf("MD5=%s;SHA1=%s;SHA256=%s;SHA512", h.Md5, h.Sha1, h.Sha256, h.Sha512) 42 | } 43 | 44 | const ( 45 | KernelProcessProviderName = "Microsoft-Windows-Kernel-Process" 46 | FilemonKernelProcessProvider = KernelProcessProviderName + ":0xff:1,2" 47 | KernelFileProviderName = "Microsoft-Windows-Kernel-File" 48 | FilemonProvider = KernelFileProviderName + ":0xff:12,14,15,16" 49 | ) 50 | 51 | var ( 52 | KernelFileProvider = etw.MustParseProvider(KernelFileProviderName) 53 | KernelProcessProvider = etw.MustParseProvider(KernelProcessProviderName) 54 | 55 | fileObjectMapping = make(map[string]*file) 56 | processMapping = make(map[uint32]*process) 57 | filemonRegex = regexp.MustCompile(".*") 58 | ) 59 | 60 | func getImageName(pid uint32) string { 61 | if p, ok := processMapping[pid]; ok { 62 | return p.image 63 | } 64 | return "UNKNOWN" 65 | } 66 | 67 | func hash(path string) (h *hashes) { 68 | var fd *os.File 69 | var err error 70 | 71 | h = &hashes{} 72 | if fd, err = os.Open(path); err != nil { 73 | h.Error = err.Error() 74 | return 75 | } 76 | defer fd.Close() 77 | 78 | md5 := md5.New() 79 | sha1 := sha1.New() 80 | sha256 := sha256.New() 81 | 82 | buf := [4096]byte{} 83 | 84 | for n, err := fd.Read(buf[:]); err == nil; n, err = fd.Read(buf[:]) { 85 | md5.Write(buf[:n]) 86 | sha1.Write(buf[:n]) 87 | sha256.Write(buf[:n]) 88 | } 89 | 90 | if err != io.EOF && err != nil { 91 | h.Error = err.Error() 92 | } 93 | 94 | h.Md5 = hex.EncodeToString(md5.Sum(nil)) 95 | h.Sha1 = hex.EncodeToString(sha1.Sum(nil)) 96 | h.Sha256 = hex.EncodeToString(sha256.Sum(nil)) 97 | 98 | return 99 | } 100 | 101 | func filemonEventRecordCB(r *etw.EventRecord) bool { 102 | // filter out our own events 103 | return r.EventHeader.ProcessId != uint32(os.Getpid()) 104 | } 105 | 106 | func filemonPreparedCB(h *etw.EventRecordHelper) (err error) { 107 | 108 | if h.ProviderGUID() == KernelProcessProvider.GUID { 109 | var image string 110 | var pid uint64 111 | 112 | switch h.EventID() { 113 | // Process start 114 | case 1: 115 | if image, err = h.GetPropertyString("ImageName"); err == nil { 116 | if pid, err = h.GetPropertyUint("ProcessID"); err == nil { 117 | pid32 := uint32(pid) 118 | processMapping[pid32] = &process{ 119 | pid: pid32, 120 | image: image, 121 | } 122 | } 123 | } 124 | // Process stop 125 | case 2: 126 | delete(processMapping, h.EventRec.EventHeader.ProcessId) 127 | default: 128 | } 129 | h.Skip() 130 | } 131 | 132 | if h.ProviderGUID() != KernelFileProvider.GUID { 133 | return 134 | } 135 | 136 | switch h.EventID() { 137 | case 12: 138 | 139 | if fo, err := h.GetPropertyString("FileObject"); err == nil { 140 | if fn, err := h.GetPropertyString("FileName"); err == nil { 141 | if filemonRegex.MatchString(fn) { 142 | fileObjectMapping[fo] = &file{name: fn} 143 | } 144 | } 145 | } 146 | 147 | // we skip file create events 148 | h.Skip() 149 | 150 | case 14: 151 | 152 | // skip file close events 153 | h.Skip() 154 | if object, err := h.GetPropertyString("FileObject"); err == nil { 155 | delete(fileObjectMapping, object) 156 | } 157 | 158 | case 15, 16: 159 | var f *file 160 | var object string 161 | var ok bool 162 | 163 | if object, err = h.GetPropertyString("FileObject"); err != nil { 164 | h.Skip() 165 | break 166 | } 167 | 168 | if f, ok = fileObjectMapping[object]; !ok { 169 | // we skip events we cannot enrich 170 | h.Skip() 171 | break 172 | } 173 | 174 | if (h.EventID() == 15 && f.flags.read) || 175 | (h.EventID() == 16 && f.flags.write) { 176 | h.Skip() 177 | break 178 | } 179 | 180 | h.SetProperty("TargetFileName", f.name) 181 | h.SetProperty("Image", getImageName(h.EventRec.EventHeader.ProcessId)) 182 | // output event will only show filename 183 | h.SelectFields("TargetFileName", "Image") 184 | f.flags.read = (h.EventID() == 15) 185 | f.flags.write = (h.EventID() == 16) 186 | 187 | default: 188 | h.Skip() 189 | } 190 | 191 | return nil 192 | } 193 | -------------------------------------------------------------------------------- /tools/etwdump/makefile: -------------------------------------------------------------------------------- 1 | MAIN_BASEN_SRC=etwdump 2 | RELEASE=$(GOPATH)/release/$(MAIN_BASEN_SRC) 3 | VERSION=$(shell git tag | tail -1 | sed 's/^v//') 4 | COMMITID=$(shell git rev-parse HEAD) 5 | 6 | # Strips symbols and dwarf to make binary smaller 7 | OPTS=-ldflags "-s -w" -trimpath 8 | ifdef DEBUG 9 | OPTS= 10 | endif 11 | 12 | all: 13 | $(MAKE) clean 14 | $(MAKE) init 15 | $(MAKE) buildversion 16 | $(MAKE) compile 17 | 18 | init: 19 | mkdir -p $(RELEASE)/windows 20 | 21 | compile: 22 | $(MAKE) windows 23 | 24 | windows: 25 | GOARCH=386 GOOS=windows go build $(OPTS) -o $(RELEASE)/windows/$(MAIN_BASEN_SRC)-v$(VERSION)-386.exe *.go 26 | GOARCH=amd64 GOOS=windows go build $(OPTS) -o $(RELEASE)/windows/$(MAIN_BASEN_SRC)-v$(VERSION)-amd64.exe *.go 27 | cd $(RELEASE)/windows; shasum -a 256 * > sha256.txt 28 | 29 | buildversion: 30 | printf "package main\n\nconst(\n version=\"$(VERSION)\"\n commitID=\"$(COMMITID)\"\n)\n" > version.go 31 | 32 | clean: 33 | rm -rf $(RELEASE)/* 34 | --------------------------------------------------------------------------------