├── .gitignore ├── .gitlab-ci.yml ├── LICENSE.txt ├── README.md ├── af_lenz.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.txt 2 | venv 3 | .idea -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: docker-gsrt-tech.af.paloaltonetworks.local/base-image/gitlabci:latest 2 | 3 | stages: 4 | - build 5 | - pypi 6 | - post 7 | 8 | before_script: 9 | - python3 -V 10 | 11 | pypi: 12 | stage: pypi 13 | script: 14 | - python3 setup.py bdist_wheel 15 | - twine upload dist/* 16 | only: 17 | refs: 18 | - master 19 | changes: 20 | - "setup.py" 21 | 22 | build-utilties-container: 23 | stage: post 24 | trigger: 25 | project: gsrt-tools/gsrt-utilities-container 26 | branch: master 27 | only: 28 | refs: 29 | - master 30 | changes: 31 | - "setup.py" 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | autofocus_lenz is distributed with an ISC license: 2 | 3 | Copyright (c) 2015 Palo Alto Networks, Inc. 4 | 5 | Permission to use, copy, modify, and distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## af_lenz 2 | 3 | ### [+] INTRO [+] 4 | 5 | The AutoFocus API exposes a wealth of dynamic analysis information about malware activities from disk to wire, which is made easily accessible for scripting through the [AutoFocus Python Client Library](https://github.com/PaloAltoNetworks/autofocus-client-library). The goal of *af_lenz.py* is to build ontop of the client library by providing a set of helpful tools to aide incident responders, or analysts, in rapidly extracting information from AutoFocus that can be used for operational intelligence. 6 | 7 | ``` 8 | usage: af_lenz.py [-h] -i -q [-o ] 9 | [-f ] [-l ] -r 10 | [-s ] [-c ] [-Q] 11 | [-w ] [-d] [-p ] 12 | 13 | Run functions to retrieve information from AutoFocus. 14 | 15 | optional arguments: 16 | -h, --help show this help message and exit 17 | -i , --ident 18 | Query identifier type for AutoFocus search. 19 | [connection, dns, email, file, file_hashes, 20 | file_query, filename, fileurl, hash, hash_list, http, 21 | ip, mutex, process, query, registry, service, tag, 22 | threat, url, user_agent] 23 | -q , --query 24 | Value to query Autofocus for. 25 | -o , --output 26 | Section of data to return. Multiple values are comma 27 | separated (no space) or "all" for everything, which is 28 | default. Sample Sections [all_apk, all_elf, 29 | apk_app_icon, apk_app_name, apk_cert_file, 30 | apk_certificate_id, apk_defined_activity, 31 | apk_defined_intent_filter, apk_defined_receiver, 32 | apk_defined_sensor, apk_defined_service, 33 | apk_digital_signer, apk_embedded_library, 34 | apk_embeded_url, apk_internal_file, apk_isrepackaged, 35 | apk_packagename, apk_requested_permission, 36 | apk_sensitive_api_call, 37 | apk_suspicious_action_monitored, 38 | apk_suspicious_api_call, apk_suspicious_file, 39 | apk_suspicious_pattern, apk_suspicious_string, 40 | apk_version_num, behavior, behavior_type, connection, 41 | default, digital_signer, dns, dropped_files, 42 | elf_commands, elf_command_action, elf_domains, 43 | elf_file_activity, elf_file_paths, elf_functions, 44 | elf_ip_address, elf_suspicious_action, 45 | elf_suspicious_behavior, elf_urls, file, http, 46 | imphash, japi, mac_embedded_file, mac_embedded_url, 47 | macro, misc, mutex, process, registry, service, 48 | summary, user_agent]. Session Sections [account_name, 49 | application, device_country_code, device_country, 50 | device_hostname, industry, business_line, 51 | device_model, device_serial, device_version, 52 | dst_country_code, dst_country, dst_ip, 53 | dst_is_private_ipdst_port, email_recipient, 54 | email_charset, email_sender, email_subject, file_name, 55 | file_url, sha256, src_country_code, src_country, 56 | src_ip, src_is_private_ipsrc_port, timestamp, 57 | upload_source, user_id, _vsys]. Meta Sections [sha256, 58 | file_type, create_date, verdict, file_size, tags, 59 | sha1, md5, ssdeep, imphash, digital_signer]. Coverage 60 | Sections [dns_sig, fileurl_sig, url_cat, wf_av_sig]. 61 | -f , --filter 62 | Filter out Benign/Grayware/Malware counts over this 63 | number, default 10,000. Uses pre-built malware 64 | filtering from AF. Use 0 for no filter. 65 | [all_suspicious, highly_suspicious, suspicious] 66 | -l , --limit 67 | Limit the number of analyzed samples, default 200. Use 68 | 0 for no limit. 69 | -r , --run 70 | Function to run. [uniq_sessions, common_artifacts, 71 | common_pieces, hash_scrape, http_scrape, dns_scrape, 72 | mutex_scrape, meta_scrape, sample_scrape, 73 | service_scrape, session_scrape, diff, tag_check, 74 | tag_info, dropped_file_scrape, coverage_scrape] 75 | -s , --special 76 | Output data formated in a special way for other tools. 77 | [af_import, bgm, count, range, tag_count, yara_rule] 78 | -c , --commonality 79 | Commonality percentage for comparison functions, 80 | default is 100 81 | -Q, --quiet Suppress any additional informational output and only 82 | return specified data. 83 | -w , --write 84 | Write output to a file instead of STDOUT. 85 | -d, --debug Enable debug logging (limited usage). 86 | -p , --platform 87 | Limit results to the specific VM platform. [android, 88 | mac, staticAnalyzer, win10, win7, winxp] 89 | ``` 90 | 91 | Quick links to examples: 92 | * [Hash Scrape function](#hash_scrape) 93 | * [Common Artifacts function](#common_artifacts) 94 | * [Common Pieces function](#common_pieces) 95 | * [Show commonality range](#range) 96 | * [HTTP Scrape function](#http_scrape) 97 | * [DNS Scrape function](#dns_scrape) 98 | * [Mutex Scrape function](#mutex_scrape) 99 | * [Unique Sessions function](#uniq_sessions) 100 | * [Generate Yara rule](#yara_rule) 101 | * [Generate AutoFocus query](#af_import) 102 | * [Control output](#section_output) 103 | * [Set commonality percent](#commonality) 104 | * [Submit complex AutoFocus queries](#complex_query) 105 | * [Analyze non-PE files](#apk_analyzer) 106 | * [Limit analyzed samples](#limit_result) 107 | * [Collect bulk sample meta data](#meta_data) 108 | * [Extract all unique entries](#extract_all) 109 | * [Quiet Output](#quiet_flag) 110 | * [Diff function](#diff) 111 | * [Count function](#count) 112 | * [Tag Count function](#tag_count) 113 | * [Suspicious/Highly Suspicious filter](#suspect_artifacts) 114 | * [Service Scrape function](#service_scrape) 115 | * [Write to file](#write_out) 116 | * [Tag Info function](#tag_info) 117 | * [Tag Check function](#tag_check) 118 | * [Coverage Scrape function](#coverage_scrape) 119 | 120 | ### [+] EXAMPLES [+] 121 | 122 | Analyzing activity of malware can be very noisy and AutoFocus provides a good way to identify whether something might be noise through the use of the B/G/M system. For each sample with a matching entry for the activity, whether its file, network, or process based, it will be added to a count for benign, grayware, and malicious samples. In this fashion, if a entry has 5 million matches to benign samples, it's likely just noise; that being said, *af_lenz.py* has a built-in filter of 10,000 matches but can be adjusted with the *-f* flag to override it. 123 | 124 | To lookup the dynamic analysis (DA) information for a particular sample, specify the identifier for the query as hash, pass the SHA256 hash, and run the "hash_lookup" function. As you'll see, it can be a large amount of data, pipe delimeted, but gives you a quick way to automate or hone in on specifics. 125 | 126 | ##### hash_scrape 127 | 128 | ``` 129 | python af_lenz.py -i hash -q 232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d -r hash_scrape 130 | 131 | {"operator":"all","children":[{"field":"sample.sha256","operator":"is","value":"232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d"}]} 132 | 133 | [+] hashes [+] 134 | 135 | 232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d 136 | 137 | [+] registry [+] 138 | 139 | sample.exe , SetValueKey , HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Run\Windows Audio Service , Value:Windows\SysWOW64\svchost_update.exe , Type:1 140 | svchost_update.exe , DeleteValueKey , HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\ProxyBypass 141 | svchost_update.exe , DeleteValueKey , HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\ProxyBypass 142 | svchost_update.exe , DeleteValueKey , HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\IntranetName 143 | 144 | ntsystem.exe , RegCreateKeyEx , HKLM , System\CurrentControlSet\Services\Tcpip\Parameters 145 | netsh.exe , RegSetValueEx , HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\AuthorizedApplications\List , WINDOWS\system32\ntsystem.exe , WINDOWS\system32\ntsystem.exe:*:Enabled:Windows ClipBook 146 | ntsystem.exe , RegSetValueEx , HKCU\Software\pb , id , 1494421186 147 | ntsystem.exe , RegSetValueEx , HKCU\Software\pb , hl , aHR0cDovL21pYmVycG9ydGVzYWwuY29tL2dvLnBocA== 148 | 149 | [+] process [+] 150 | 151 | sample.exe , created , , Windows\SysWOW64\svchost_update.exe , Windows\SysWOW64\svchost_update.exe 152 | svchost_update.exe , created , , Windows\SysWOW64\netsh.exe , Windows\System32\netsh.exe firewall add allowedprogram Windows\SysWOW64\svchost_update.exe Windows Audio Service ENABLE 153 | svchost_update.exe , terminated , , Windows\SysWOW64\netsh.exe 154 | unknown , terminated , , Windows\SysWOW64\svchost_update.exe 155 | 156 | ntsystem.exe , LoadLibraryExW , DNSAPI.dll , , 0 157 | ntsystem.exe , LoadLibraryExW , rasadhlp.dll , , 0 158 | ntsystem.exe , LoadLibraryExW , hnetcfg.dll , , 0 159 | ntsystem.exe , LoadLibraryExW , WINDOWS\System32\wshtcpip.dll , , 0 160 | 161 | [+] misc [+] 162 | 163 | svchost_update.exe , SetWindowLong , SystemProcess , GWL_WNDPROC 164 | svchost_update.exe , IsDebuggerPresent 165 | ntsystem.exe , SetWindowLong , SystemProcess , GWL_WNDPROC 166 | 167 | [+] mutex [+] 168 | 169 | svchost_update.exe , CreateMutexW , Local\!IETld!Mutex 170 | ntsystem.exe , CreateMutexW , c:!documents and settings!administrator!local settings!temporary internet files!content.ie5! 171 | ntsystem.exe , CreateMutexW , c:!documents and settings!administrator!cookies! 172 | ntsystem.exe , CreateMutexW , c:!documents and settings!administrator!local settings!history!history.ie5! 173 | ntsystem.exe , CreateMutexW , WininetConnectionMutex 174 | ntsystem.exe , CreateMutexW , 175 | 176 | [+] http [+] 177 | 178 | markovqwesta.com , GET , /que.php , pb 179 | 93.189.40.225 , GET , /wp-trackback.php?proxy=46.165.222.212%3A9506&secret=BER5w4evtjszw4MBRW , 180 | 181 | [+] dns [+] 182 | 183 | markovqwesta.com , 193.235.147.11 , A 184 | iholpforyou4.com , , NXDOMAIN 185 | markovqwesta.com , ns4.cnmsn.com , NS 186 | markovqwesta.com , ns3.cnmsn.com , NS 187 | 188 | [+] behavior_desc [+] 189 | 190 | informational , 0.1 , A process running on the system may start additional processes to perform actions in the background. This behavior is common to legitimate software as well as malware. , process , 6 , Started a process 191 | medium , 0.4 , Legitimate software typically uses well-known application protocols to communicate over a network. In some cases, however, legitimate software may use proprietary protocols. Malware commonly uses custom protocols to avoid detection, and a sample that generates unknown TCP or UDP traffic in this way is often malicious. , unknown_traffic , 7 , Generated unknown TCP or UDP traffic 192 | high , 0.45 , Malware typically communicates back to an attacker via a command-and-control server. This command-and-control server is usually addressed in the malware as a domain name. To avoid easy identification of malicious domain names, the attacker may use a domain generation algorithm (DGA) to address a large number of dynamically generated domains, most of which are not registered. , nx_domain , 8 , Connected to an unregistered domain name 193 | informational , 0.1 , The Windows Registry houses system configuration settings and options, including information about installed applications, services, and drivers. Malware often modifies registry data to establish persistence on the system and avoid detection. , registry , 13 , Modified the Windows Registry 194 | 195 | high , 0.45 , The Windows system folder contains configuration files and executables that control the underlying functions of the system. Malware often places executables in this folder to avoid detection. , sys_exe , 34 , Created an executable file in the Windows system folder 196 | low , 0.3 , User folders are storage locations for music, pictures, downloads, and other user-specific files. Legitimate applications rarely place executable content in these folders, while malware often does so to avoid detection. , create_doc_exe , 35 , Created an executable file in a user folder 197 | medium , 0.4 , Windows services are background applications that are typically invisible to users. Unlike processes, services can run when no user is logged on. Malware often installs services to establish persistence on the system, or as a precursor to loading malicious device drivers. , install_service , 37 , Installed a Windows service 198 | medium , 0.45 , Most legitimate HTTP connections are established using a hostname, which is ultimately resolved to an IP address. Malware often connects directly to an IP address to avoid hostname-based blocking. , http_direct_ip , 86 , Connected directly to an IP address over HTTP 199 | 200 | [+] behavior_type [+] 201 | 202 | process 203 | unknown_traffic 204 | nx_domain 205 | registry 206 | 207 | sys_exe 208 | create_doc_exe 209 | install_service 210 | http_direct_ip 211 | 212 | [+] connection [+] 213 | 214 | unknown , tcp , 193.235.147.11:80 , SE 215 | ntsystem.exe , tcp-connection , 93.189.40.225:80 , , RU 216 | ntsystem.exe , tcp-connection , 193.235.147.11:80 , , SE 217 | ntsystem.exe , tcp-connection , 46.165.222.212:9505 , , DE 218 | ntsystem.exe , tcp-connection , 46.165.222.212:495 , , DE 219 | 220 | [+] file [+] 221 | 222 | sample.exe , Write , Windows\SysWOW64\9125y5yta.dat 223 | sample.exe , Create , Windows\SysWOW64\svchost_update.exe , 00130197 , 00000044 224 | sample.exe , Write , Windows\SysWOW64\svchost_update.exe 225 | svchost.exe , Write , Windows\System32\wdi\{86432a0b-3c7d-4ddf-a89c-172faa90485d}\{d873ef00-5982-4c2b-8a78-ea57c88fbba1}\snapshot.etl 226 | 227 | unknown , create , C:\Documents and Settings\Administrator\Local Settings\Temp\df4.tmp.exe , md5=1197f290bae092c70a6cf07a223ed8bc , sha1=5e9a3cc80ea4d2b0b31d2a7e8750cd5f1ce16dc7 , sha256=4adb44b3cd6fe503d218067307302628c3a0a895acfe03998c24c8f3d561dd15 228 | unknown , create , C:\Documents and Settings\Administrator\Local Settings\Temporary Internet Files\Content.IE5\LDKH2A5D\book[2].htm , md5=ff4e1927bdf5ad3c6e752a8cb02db5d5 , sha1=ac473bd177e1e9ca7ef74d92eb4a9392bcc4a31e , sha256=4677cb12006da7721110ebc6b763ceb52eaf3e516540f57a7704c6aaea76bc79 229 | unknown , create , C:\Documents and Settings\Administrator\Local Settings\Temporary Internet Files\Content.IE5\VPKKM73P\book[2].htm 230 | unknown , delete , C:\Documents and Settings\Administrator\Local Settings\Temporary Internet Files\Content.IE5\K1XHOOEA\book[1].htm , md5=ff4e1927bdf5ad3c6e752a8cb02db5d5 , sha1=ac473bd177e1e9ca7ef74d92eb4a9392bcc4a31e , sha256=4677cb12006da7721110ebc6b763ceb52eaf3e516540f57a7704c6aaea76bc79 231 | 232 | [+] processed 1 hashes with a BGM filter of 10000 [+] 233 | ``` 234 | 235 | You can also use the *-o* flag to specify only the sections of output you're interested in. 236 | 237 | ##### section_output 238 | 239 | ``` 240 | python af_lenz.py -i hash -q 232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d -r hash_scrape -o http,dns,connection 241 | 242 | {"operator":"all","children":[{"field":"sample.sha256","operator":"is","value":"232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d"}]} 243 | 244 | [+] hashes [+] 245 | 246 | 232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d 247 | 248 | [+] http [+] 249 | 250 | markovqwesta.com , GET , /que.php , pb 251 | 93.189.40.225 , GET , /wp-trackback.php?proxy=46.165.222.212%3A9506&secret=BER5w4evtjszw4MBRW , 252 | 253 | [+] dns [+] 254 | 255 | markovqwesta.com , 193.235.147.11 , A 256 | iholpforyou4.com , , NXDOMAIN 257 | markovqwesta.com , ns4.cnmsn.com , NS 258 | markovqwesta.com , ns3.cnmsn.com , NS 259 | 260 | [+] connection [+] 261 | 262 | unknown , tcp , 193.235.147.11:80 , SE 263 | ntsystem.exe , tcp-connection , 93.189.40.225:80 , , RU 264 | ntsystem.exe , tcp-connection , 193.235.147.11:80 , , SE 265 | ntsystem.exe , tcp-connection , 46.165.222.212:9505 , , DE 266 | ntsystem.exe , tcp-connection , 46.165.222.212:495 , , DE 267 | 268 | [+] processed 1 hashes with a BGM filter of 10000 [+] 269 | ``` 270 | 271 | Using the previous data, we see an HTTP request that looks interesting and want to investigate further. It can be very cumbersome to go through multiple samples, so the "common_artifacts" function can be run to compare every sample and report back only items that exist across the set. For this query, we are matching any samples that contain the domain in question. 272 | 273 | ##### common_artifacts 274 | 275 | ``` 276 | python af_lenz.py -i http -q markovqwesta.com -r common_artifacts 277 | 278 | {"operator":"all","children":[{"field":"sample.tasks.http","operator":"contains","value":"markovqwesta.com"}]} 279 | 280 | [+] hashes [+] 281 | 282 | 232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d 283 | cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03 284 | c19487136ebc82a38e13264ca8bd1b7983039db103d2520c52e49f40ac35b1db 285 | c550a0730c9cf10751a3236ef57fafb5af844bef3874855a215519a9ffcec348 286 | 287 | 234203c3e40184c430331c266b4108db94b3f68258410b7592da81d6abc88b7d 288 | 1963a881beefd720648ca9a28c578b4f10f6ea38a8dfab436756fd64dc418bc3 289 | f1485e53403de8c654783ce3e0adf754639542e41c2a89b92843ce8ecdeb4646 290 | 23e9815fe25321b0349e8c6fc22473914a306d27a9d8cae2872396cf7a14c099 291 | 292 | [+] dns [+] 293 | 294 | markovqwesta.com , ns4.cnmsn.com , NS 295 | markovqwesta.com , ns3.cnmsn.com , NS 296 | 297 | [+] processed 10 hashes with a BGM filter of 10000 [+] 298 | ``` 299 | 300 | One problem with DA is that, by its very nature, its dynamic and has the potential to provide inconsistent results - malware may not run due to certain features being enabled, software being installed, so on and so forth. Continuing with the previous example, instead of a 100% match across all samples, we'll loosen it up to matches across 70% of the set. 301 | 302 | ##### commonality 303 | 304 | ``` 305 | python af_lenz.py -i http -q markovqwesta.com -r common_artifacts -c 70 306 | 307 | {"operator":"all","children":[{"field":"sample.tasks.http","operator":"contains","value":"markovqwesta.com"}]} 308 | 309 | [+] hashes [+] 310 | 311 | 232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d 312 | cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03 313 | c19487136ebc82a38e13264ca8bd1b7983039db103d2520c52e49f40ac35b1db 314 | c550a0730c9cf10751a3236ef57fafb5af844bef3874855a215519a9ffcec348 315 | 316 | 234203c3e40184c430331c266b4108db94b3f68258410b7592da81d6abc88b7d 317 | 1963a881beefd720648ca9a28c578b4f10f6ea38a8dfab436756fd64dc418bc3 318 | f1485e53403de8c654783ce3e0adf754639542e41c2a89b92843ce8ecdeb4646 319 | 23e9815fe25321b0349e8c6fc22473914a306d27a9d8cae2872396cf7a14c099 320 | 321 | [+] user_agent [+] 322 | 323 | pb 324 | 325 | [+] http [+] 326 | 327 | markovqwesta.com , GET , /que.php , pb 328 | 329 | [+] dns [+] 330 | 331 | markovqwesta.com , 193.235.147.11 , A 332 | markovqwesta.com , ns4.cnmsn.com , NS 333 | markovqwesta.com , ns3.cnmsn.com , NS 334 | 335 | [+] connection [+] 336 | 337 | unknown , tcp , 193.235.147.11:80 , SE 338 | 339 | [+] processed 10 hashes with a BGM filter of 10000 [+] 340 | ``` 341 | 342 | The "common_pieces" function, which is the similar to the "common_artifacts" function, further breaks up each entry into individual pieces. This is beneficial when malware might inject into random processes or use unique names each time that won't match across samples. All comparison functions can use the *-c* flag to specify the commonality match percentage. In this next query we are search for common pieces for samples matching the Unit 42 Locky tag with a commonality match of 90%. 343 | 344 | ##### common_pieces 345 | 346 | ``` 347 | python af_lenz.py -i tag -q Unit42.Locky -r common_pieces -c 90 348 | 349 | {"operator":"all","children":[{"field":"sample.tag","operator":"is in the list","value":["Unit42.Locky"]}]} 350 | 351 | [+] hashes [+] 352 | 353 | e720f917cd8a02b0372b85068844e132c42ea2c97061b81d378b5a73f9344003 354 | a486ff7e775624da2283ede1d3959c784afe476b0a69ce88cd12c7238b15c9e6 355 | 3297f99631c92aeb7b5fcccfac1c0b0e54880e399cf10a659b4448e6fe339f9d 356 | 7f540e391b55221f7696031471b6f8d2068677a67ed8782d52a67872096d23a2 357 | 358 | 1d8cc4e8416b5ac16864583e8bb0d8f8d8ad4b32de7de111067c38da0cfc57b1 359 | a4770e01d7734498834cc80f762890f332f1156198990b46217f63aa5b916030 360 | 13bd70822009e07f1d0549e96b8a4aec0ade07bea2c28d42d782bacc11259cf5 361 | b7a593e6b7813d9fc40f435ffe9b080cd0975b05bc47f1e733870fc0af289fdd 362 | 363 | [+] registry [+] 364 | 365 | HKCU\Software\Locky\pubkey 366 | completed 367 | pubkey 368 | HKLM\SOFTWARE\Microsoft\WBEM\CIMOM\LastServiceStart 369 | 370 | paytext 371 | HKCU\Software\Microsoft\Windows\ShellNoRoam\MUICache\WINDOWS\system32\shimgvw.dll 372 | HKCU\Software\Locky\completed 373 | WallpaperStyle 374 | 375 | [+] process [+] 376 | 377 | Windows\system32\NOTEPAD.EXE 378 | Windows\System32\taskeng.exe 379 | rundll32.exe WINDOWS\system32\shimgvw.dll 380 | 142E1D688EF0568370C37187FD9F2351D7DDEDA574F8BFA9B0FA4EF42DB85AA2 381 | 382 | CreateProcessInternalW 383 | F2C7BB8ACC97F92E987A2D4087D021B1 384 | WINDOWS\system32\rundll32.exe 385 | terminated 386 | 387 | [+] http [+] 388 | 389 | /main.php 390 | POST 391 | 392 | [+] behavior_type [+] 393 | 394 | autostart 395 | open_process_dup_handle 396 | http_post 397 | pending_file_rename 398 | 399 | process 400 | file 401 | registry 402 | create_doc_exe 403 | 404 | [+] connection [+] 405 | 406 | tcp 407 | unknown 408 | 409 | [+] file [+] 410 | 411 | C:\Documents and Settings\Administrator\Desktop\_Locky_recover_instructions.txt 412 | Documents and Settings\All Users\Documents\My Pictures\Sample Pictures\Blue hills.jpg 413 | Documents and Settings\Administrator\Cookies\_Locky_recover_instructions.txt 414 | Documents and Settings\All Users\Start Menu\Programs\Startup\_Locky_recover_instructions.txt 415 | 416 | 000900a8 417 | 00000060 418 | 0011c017 419 | Users\Public\Pictures\Sample Pictures\Chrysanthemum.jpg 420 | 421 | [+] processed 1364 hashes with a BGM filter of 10000 [+] 422 | ``` 423 | 424 | There is also a special output for the commonality functions, called "range", which prints out the percentage of commonality. For example, if you set a lower commonality requirement, you can see in which bands of commonality artifacts fall. This may be useful of identifying trends in subsets of the samples or how strong an artifact might be. 425 | 426 | ##### range 427 | 428 | ``` 429 | python af_lenz.py -i dns -q markovqwesta.com -r common_artifacts -c 30 -s range 430 | 431 | {"operator":"all","children":[{"field":"alias.domain","operator":"contains","value":"markovqwesta.com"}]} 432 | 433 | [+] hashes [+] 434 | 435 | 639d03fb6465a94189fb5b29887afe0965a95c9a7778fb624b92eef6ed22b7bb 436 | c19487136ebc82a38e13264ca8bd1b7983039db103d2520c52e49f40ac35b1db 437 | 232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d 438 | 1963a881beefd720648ca9a28c578b4f10f6ea38a8dfab436756fd64dc418bc3 439 | 440 | cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03 441 | 23e9815fe25321b0349e8c6fc22473914a306d27a9d8cae2872396cf7a14c099 442 | ffe9fb1f9ef7465c99edfe17ccce496172cba47357b2caff6720900a0f6426b2 443 | c97bd3d159222bfe650647aefb92fd13b2e590f8d5dd5781110a0cf61958fc33 444 | 445 | [+] registry [+] 446 | 447 | 33 | sample.exe , SetValueKey , HKCU\Software\kg\cat , 448 | 33 | sample.exe , DeleteValueKey , HKCU\Software\kg\chk , 449 | 33 | sample.exe , DeleteValueKey , HKCU\Software\kg\main , 450 | 33 | sample.exe , SetValueKey , HKCU\Software\kg\chk , 451 | 452 | 33 | sample.exe , DeleteValueKey , HKCU\Software\kg\main 453 | 33 | sample.exe , RegCreateKeyEx , HKCU , SOFTWARE\kg 454 | 33 | sample.exe , DeleteValueKey , HKCU\Software\kg\name 455 | 33 | sample.exe , DeleteValueKey , HKCU\Software\kg\chk 456 | 457 | [+] user_agent [+] 458 | 459 | 66 | pb 460 | 461 | [+] mutex [+] 462 | 463 | 33 | sample.exe , CreateMutexW , PB_SN_MUTEX_GL_F348B3A2387 464 | 465 | [+] http [+] 466 | 467 | 75 | markovqwesta.com , GET , /que.php , pb 468 | 469 | [+] dns [+] 470 | 471 | 75 | markovqwesta.com , 193.235.147.11 , A 472 | 83 | markovqwesta.com , ns4.cnmsn.com , NS 473 | 83 | markovqwesta.com , ns3.cnmsn.com , NS 474 | 475 | [+] behavior_desc [+] 476 | 477 | 100 | informational , 0.1 , The Windows Registry houses system configuration settings and options, including information about installed applications, services, and drivers. Malware often modifies registry data to establish persistence on the system and avoid detection. , registry , 13 , Modified the Windows Registry 478 | 100 | informational , 0.1 , Legitimate software creates or modifies files to preserve data across system restarts. Malware may create or modify files to deliver malicious payloads or maintain persistence on a system. , file , 3 , Created or modified a file 479 | 75 | medium , 0.4 , Windows services are background applications that are typically invisible to users. Unlike processes, services can run when no user is logged on. Malware often installs services to establish persistence on the system, or as a precursor to loading malicious device drivers. , install_service , 37 , Installed a Windows service 480 | 58 | high , 0.8 , The Windows Registry Run keys allow an application to specify that it should be launched during system startup. Malware often leverages this mechanism to ensure that it will be run each time the system boots up, and may run content out of a user folder to avoid detection. , autostart_from_local_dir , 77 , Modified the Windows Registry to enable auto-start for a file in a user folder 481 | 482 | 100 | medium , 0.4 , The Windows Registry Run keys allow an application to specify that it should be launched during system startup. Malware often leverages this mechanism to establish persistence on the system and ensure that it will be run each time the system boots up. , autostart , 14 , Modified the Windows Registry to enable auto-start 483 | 66 | medium , 0.4 , Legitimate software typically uses well-known application protocols to communicate over a network. In some cases, however, legitimate software may use proprietary protocols. Malware commonly uses custom protocols to avoid detection, and a sample that generates unknown TCP or UDP traffic in this way is often malicious. , unknown_traffic , 7 , Generated unknown TCP or UDP traffic 484 | 91 | low , 0.2 , Rather than communicate directly with a server, a client may route requests through a proxy. If the proxy is malicious, it may modify what a user sees when accessing web pages or even execute a man-in-the-middle (MITM) attack, potentially gaining access to sensitive user information. , browser_proxy , 49 , Modified proxy settings for Internet Explorer 485 | 58 | medium , 0.45 , Malware analysis environments have a limited amount of time in which to execute code and deliver a verdict. To subvert this process, malware often delays execution, or "sleeps," for a long period, allowing it to avoid detection. , long_sleep , 84 , Attempted to sleep for a long period 486 | 487 | [+] behavior_type [+] 488 | 489 | 50 | external_netsh 490 | 33 | http_short_headers 491 | 50 | process 492 | 50 | file 493 | 494 | 41 | delete_itself 495 | 41 | ie_security 496 | 50 | create_doc_exe 497 | 50 | unpack_write_section 498 | 499 | [+] connection [+] 500 | 501 | 66 | unknown , tcp , 193.235.147.11:80 , SE 502 | 503 | [+] file [+] 504 | 505 | 33 | unknown , create , C:\Documents and Settings\Administrator\Local Settings\Temporary Internet Files\Content.IE5\VPKKM73P\book[2].htm 506 | 33 | sample.exe , Write , Windows\SysWOW64\9125y5yta.dat 507 | 33 | unknown , create , C:\Documents and Settings\Administrator\Local Settings\Temporary Internet Files\Content.IE5\VPKKM73P\book[1].htm , md5=46788efce76ebf3e09fc844af99c5309 , sha1=d25ec96232263c0eb834d9c7b437dbe97029a809 , sha256=3376bb271f1e1e7b2e0eb28475f8bab01ed69627861682ac809a732cb023d230 508 | 33 | sample.exe , Write , WINDOWS\system32\9125y5yta.dat , 509 | 510 | [+] processed 12 hashes with a BGM filter of 10000 [+] 511 | ``` 512 | 513 | In addition to the comparison functions, there are scraping functions which will iterate over each sample identified and return all unique data in their respective sections. Below are all HTTP requests made by samples with a IP in their DNS section (resolved to). 514 | 515 | ##### http_scrape 516 | 517 | ``` 518 | python af_lenz.py -i dns -q 193.235.147.11 -r http_scrape 519 | 520 | {"operator":"all","children":[{"field":"alias.domain","operator":"contains","value":"193.235.147.11"}]} 521 | 522 | [+] hashes [+] 523 | 524 | 232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d 525 | cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03 526 | e4b35695fdf6583ca382647bf4587a2ca225cabf9aa7c954489414ea4f433a9e 527 | 75e14aaef0ff2b851ce9775a95a1f54624030786134faf29c0e80a675b9c310e 528 | 529 | c19487136ebc82a38e13264ca8bd1b7983039db103d2520c52e49f40ac35b1db 530 | f1485e53403de8c654783ce3e0adf754639542e41c2a89b92843ce8ecdeb4646 531 | 23e9815fe25321b0349e8c6fc22473914a306d27a9d8cae2872396cf7a14c099 532 | 118577d6c021c14cbd9c7226475c982d2ce230568295b86f3104860e544f7317 533 | 534 | [+] http [+] 535 | 536 | hxxp://markovqwesta.com/que.php 537 | hxxp://93.189.40.225/wp-trackback.php?proxy=46.165.222.212%3A9506&secret=BER5w4evtjszw4MBRW 538 | hxxp://iholpforyou4.com/d_index.php 539 | hxxp://80.78.242.47/pointer.php?proxy=217.172.179.88%3A14452&secret=BER5w4evtjszw4MBRW 540 | 541 | hxxp://80.78.242.47/pointer.php?proxy=194.247.12.49%3A27123&secret=BER5w4evtjszw4MBRW 542 | hxxp://80.78.242.47/pointer.php?proxy=69.64.32.110%3A23622&secret=BER5w4evtjszw4MBRW 543 | hxxp://93.189.40.196/i.php?proxy=46.38.51.49%3A32045&secret=BER5w4evtjszw4MBRW 544 | hxxp://66.85.139.195/phinso.php?proxy=46.165.222.212%3A29786&secret=BER5w4evtjszw4MBRW 545 | 546 | [+] processed 17 hashes with a BGM filter of 10000 [+] 547 | ``` 548 | 549 | DNS scrape of three hashes pasted as a comma separated list. 550 | 551 | ##### dns_scrape 552 | 553 | ``` 554 | python af_lenz.py -i hash_list -q 232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d,cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03,e4b35695fdf6583ca382647bf4587a2ca225cabf9aa7c954489414ea4f433a9e -r dns_scrape 555 | 556 | {"operator":"all","children":[{"field":"sample.sha256","operator":"is in the list","value":["232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d", "cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03", "e4b35695fdf6583ca382647bf4587a2ca225cabf9aa7c954489414ea4f433a9e"]}]} 557 | 558 | [+] hashes [+] 559 | 560 | 232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d 561 | cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03 562 | e4b35695fdf6583ca382647bf4587a2ca225cabf9aa7c954489414ea4f433a9e 563 | 564 | [+] dns [+] 565 | 566 | markovqwesta.com 567 | iholpforyou4.com 568 | truedonell.com 569 | 570 | [+] processed 3 hashes with a BGM filter of 10000 [+] 571 | ``` 572 | 573 | Mutex scrape of the same three hashes. 574 | 575 | ##### mutex_scrape 576 | 577 | ``` 578 | python af_lenz.py -i hash_list -q 232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d,cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03,e4b35695fdf6583ca382647bf4587a2ca225cabf9aa7c954489414ea4f433a9e -r mutex_scrape 579 | 580 | {"operator":"all","children":[{"field":"sample.sha256","operator":"is in the list","value":["232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d", "cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03", "e4b35695fdf6583ca382647bf4587a2ca225cabf9aa7c954489414ea4f433a9e"]}]} 581 | 582 | [+] hashes [+] 583 | 584 | 232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d 585 | cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03 586 | e4b35695fdf6583ca382647bf4587a2ca225cabf9aa7c954489414ea4f433a9e 587 | 588 | [+] mutex [+] 589 | 590 | Local\!IETld!Mutex 591 | c:!documents and settings!administrator!local settings!temporary internet files!content.ie5! 592 | c:!documents and settings!administrator!cookies! 593 | c:!documents and settings!administrator!local settings!history!history.ie5! 594 | WininetConnectionMutex 595 | 596 | IESQMMUTEX_0_208 597 | PB_SN_MUTEX_GL_F348B3A2387 598 | PB_MAIN_MUTEX_GL_63785462387 599 | PB_SCH_MUTEX_GL_A58B78398f17 600 | 601 | [+] processed 3 hashes with a BGM filter of 10000 [+] 602 | ``` 603 | 604 | Another common use-case might be to look at the session data related to how malware was delivered. By using the "uniq_session" function, you can view the session data attached to the samples in AutoFocus. In this query, we search for samples matching a unique mutex and pull back their session data. 605 | 606 | ##### uniq_sessions 607 | 608 | ``` 609 | python af_lenz.py -i mutex -q M_Test -r uniq_sessions 610 | 611 | {"operator":"all","children":[{"field":"sample.tasks.mutex","operator":"contains","value":"M_Test"}]} 612 | 613 | [+] filename [+] 614 | 615 | 82300c42-320c-4348-afa7-39abb7f1d5f2_a945e5bc9ca9f26be7315f3dd5beae8a89777c7830a466bcc45c06011ab2b903 616 | a945e5bc9ca9f26be7315f3dd5beae8a89777c7830a466bcc45c06011ab2b90 617 | 8afb8cfd3e73219b3fe25491ea8cbfb42b335cec425eb984b8dedc72c6d0ea7f.file 618 | sample 619 | 620 | hda.exe.bin 621 | updxs.exe 622 | lin12.exe 623 | skaj1.exe 624 | 625 | [+] application [+] 626 | 627 | Manual Upload 628 | web-browsing 629 | http-proxy 630 | naver-mail 631 | ftp 632 | 633 | [+] country [+] 634 | 635 | Korea Republic Of 636 | United States 637 | Australia 638 | China 639 | Italy 640 | Canada 641 | Viet Nam 642 | 643 | [+] industry [+] 644 | 645 | High Tech 646 | Wholesale and Retail 647 | Manufacturing 648 | Higher Education 649 | Government 650 | Hospitality 651 | 652 | [+] processed 1056 sessions [+] 653 | ``` 654 | 655 | The above shows a varied distribution throughout industry and country (non-targeted most likely), and some additional filenames you may want to search for. 656 | 657 | There are also a few special ways you can take the data returned from the functions and output them for other tools. Below is creating a yara rule out of 4 sections of DA for a hash. 658 | 659 | ##### yara_rule 660 | 661 | ``` 662 | python af_lenz.py -i hash -q cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03 -o connection,dns,http,mutex -r hash_scrape -s yara_rule 663 | 664 | {"operator":"all","children":[{"field":"sample.sha256","operator":"is","value":"cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03"}]} 665 | 666 | [+] hashes [+] 667 | 668 | cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03 669 | 670 | [+] connection [+] 671 | 672 | winlsm.exe , connect , 91.185.215.141:989 , 2 , SI 673 | winlsm.exe , connect , 193.235.147.11:80 , 2 , SE 674 | winlsm.exe , connect , 46.36.221.85:80 , 2 , EU 675 | smss-mon.exe , tcp-connection , 193.235.147.11:80 , , SE 676 | smss-mon.exe , tcp-connection , 46.36.221.85:80 , , EU 677 | smss-mon.exe , tcp-connection , 217.172.179.88:989 , , DE 678 | smss-mon.exe , tcp-connection , 217.172.179.88:14450 , , DE 679 | smss-mon.exe , tcp-connection , 80.78.242.47:80 , , RU 680 | 681 | [+] dns [+] 682 | 683 | iholpforyou4.com , 46.36.221.85 , A 684 | markovqwesta.com , 193.235.147.11 , A 685 | markovqwesta.com , ns4.cnmsn.com , NS 686 | iholpforyou4.com , ns4.cnmsn.com , NS 687 | iholpforyou4.com , ns3.cnmsn.com , NS 688 | markovqwesta.com , ns3.cnmsn.com , NS 689 | 690 | [+] http [+] 691 | 692 | markovqwesta.com , GET , /que.php , pb 693 | iholpforyou4.com , GET , /d_index.php , pb 694 | 80.78.242.47 , GET , /pointer.php?proxy=217.172.179.88%3A14452&secret=BER5w4evtjszw4MBRW , 695 | 696 | [+] mutex [+] 697 | 698 | winlsm.exe , CreateMutexW , Local\!IETld!Mutex 699 | winlsm.exe , CreateMutexW , IESQMMUTEX_0_208 700 | smss-mon.exe , CreateMutexW , c:!documents and settings!administrator!local settings!temporary internet files!content.ie5! 701 | smss-mon.exe , CreateMutexW , c:!documents and settings!administrator!cookies! 702 | smss-mon.exe , CreateMutexW , c:!documents and settings!administrator!local settings!history!history.ie5! 703 | smss-mon.exe , CreateMutexW , WininetConnectionMutex 704 | smss-mon.exe , CreateMutexW , 705 | 706 | [+] processed 1 hashes with a BGM filter of 10000 [+] 707 | 708 | [+] yara rule [+] 709 | 710 | rule autogen_afLenz 711 | { 712 | // Namespace(commonality=100, filter=10000, ident='hash', limit=200, output='connection,dns,http,mutex', query=u'cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03', run='hash_scrape', special='yara_rule') 713 | 714 | strings: 715 | $connection_0 = "91.185.215.141" 716 | $connection_1 = "193.235.147.11" 717 | $connection_2 = "46.36.221.85" 718 | $connection_3 = "193.235.147.11" 719 | $connection_4 = "46.36.221.85" 720 | $connection_5 = "217.172.179.88" 721 | $connection_6 = "217.172.179.88" 722 | $connection_7 = "80.78.242.47" 723 | $dns_0 = "iholpforyou4.com" wide ascii 724 | $dns_2 = "markovqwesta.com" wide ascii 725 | $dns_5 = "ns4.cnmsn.com" wide ascii 726 | $dns_9 = "ns3.cnmsn.com" wide ascii 727 | $http_1 = "/que.php" wide ascii 728 | $http_4 = "/d_index.php" wide ascii 729 | $http_7 = "/pointer.php?proxy=217.172.179.88%3A14452&secret=BER5w4evtjszw4MBRW" wide ascii 730 | 731 | condition: 732 | 1 of ($connection*, $http*, $dns*) /* Adjust as needed for accuracy */ 733 | } 734 | ``` 735 | 736 | You can also build an AutoFocus query based on the output. 737 | 738 | ##### af_import 739 | 740 | ``` 741 | python af_lenz.py -i hash -q cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03 -o connection,dns,http,mutex -r hash_scrape -s af_import 742 | 743 | {"operator":"all","children":[{"field":"sample.sha256","operator":"is","value":"cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03"}]} 744 | 745 | [+] hashes [+] 746 | 747 | cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03 748 | 749 | [+] connection [+] 750 | 751 | winlsm.exe , connect , 91.185.215.141:989 , 2 , SI 752 | winlsm.exe , connect , 193.235.147.11:80 , 2 , SE 753 | winlsm.exe , connect , 46.36.221.85:80 , 2 , EU 754 | smss-mon.exe , tcp-connection , 193.235.147.11:80 , , SE 755 | smss-mon.exe , tcp-connection , 46.36.221.85:80 , , EU 756 | smss-mon.exe , tcp-connection , 217.172.179.88:989 , , DE 757 | smss-mon.exe , tcp-connection , 217.172.179.88:14450 , , DE 758 | smss-mon.exe , tcp-connection , 80.78.242.47:80 , , RU 759 | 760 | [+] dns [+] 761 | 762 | iholpforyou4.com , 46.36.221.85 , A 763 | markovqwesta.com , 193.235.147.11 , A 764 | markovqwesta.com , ns4.cnmsn.com , NS 765 | iholpforyou4.com , ns4.cnmsn.com , NS 766 | 767 | markovqwesta.com , ns4.cnmsn.com , NS 768 | iholpforyou4.com , ns4.cnmsn.com , NS 769 | iholpforyou4.com , ns3.cnmsn.com , NS 770 | markovqwesta.com , ns3.cnmsn.com , NS 771 | 772 | [+] http [+] 773 | 774 | markovqwesta.com , GET , /que.php , pb 775 | iholpforyou4.com , GET , /d_index.php , pb 776 | iholpforyou4.com , GET , /d_index.php , pb 777 | 80.78.242.47 , GET , /pointer.php?proxy=217.172.179.88%3A14452&secret=BER5w4evtjszw4MBRW , 778 | markovqwesta.com , GET , /que.php , pb 779 | 780 | [+] mutex [+] 781 | 782 | winlsm.exe , CreateMutexW , Local\!IETld!Mutex 783 | winlsm.exe , CreateMutexW , IESQMMUTEX_0_208 784 | smss-mon.exe , CreateMutexW , c:!documents and settings!administrator!local settings!temporary internet files!content.ie5! 785 | smss-mon.exe , CreateMutexW , c:!documents and settings!administrator!cookies! 786 | smss-mon.exe , CreateMutexW , c:!documents and settings!administrator!local settings!history!history.ie5! 787 | smss-mon.exe , CreateMutexW , WininetConnectionMutex 788 | smss-mon.exe , CreateMutexW , 789 | 790 | [+] processed 1 hashes with a BGM filter of 10000 [+] 791 | 792 | [+] af import query [+] 793 | 794 | {"operator":"all","children":[{"field":"sample.tasks.connection","operator":"contains","value":"91.185.215.141:989"},{"field":"sample.tasks.connection","operator":"contains","value":"193.235.147.11:80"},{"field":"sample.tasks.connection","operator":"contains","value":"46.36.221.85:80"},{"field":"sample.tasks.connection","operator":"contains","value":"193.235.147.11:80"},{"field":"sample.tasks.connection","operator":"contains","value":"46.36.221.85:80"},{"field":"sample.tasks.connection","operator":"contains","value":"217.172.179.88:989"},{"field":"sample.tasks.connection","operator":"contains","value":"217.172.179.88:14450"},{"field":"sample.tasks.connection","operator":"contains","value":"80.78.242.47:80"},{"field":"sample.tasks.dns","operator":"contains","value":"iholpforyou4.com , 46.36.221.85 , A"},{"field":"sample.tasks.dns","operator":"contains","value":"markovqwesta.com , 193.235.147.11 , A"},{"field":"sample.tasks.dns","operator":"contains","value":"markovqwesta.com , ns4.cnmsn.com , NS"},{"field":"sample.tasks.dns","operator":"contains","value":"iholpforyou4.com , ns4.cnmsn.com , NS"},{"field":"sample.tasks.dns","operator":"contains","value":"iholpforyou4.com , ns3.cnmsn.com , NS"},{"field":"sample.tasks.dns","operator":"contains","value":"markovqwesta.com , ns3.cnmsn.com , NS"},{"field":"sample.tasks.dns","operator":"contains","value":"iholpforyou4.com , 46.36.221.85 , A"},{"field":"sample.tasks.dns","operator":"contains","value":"markovqwesta.com , 193.235.147.11 , A"},{"field":"sample.tasks.dns","operator":"contains","value":"markovqwesta.com , ns4.cnmsn.com , NS"},{"field":"sample.tasks.dns","operator":"contains","value":"iholpforyou4.com , ns4.cnmsn.com , NS"},{"field":"sample.tasks.dns","operator":"contains","value":"iholpforyou4.com , ns3.cnmsn.com , NS"},{"field":"sample.tasks.dns","operator":"contains","value":"markovqwesta.com , ns3.cnmsn.com , NS"},{"field":"sample.tasks.http","operator":"contains","value":"markovqwesta.com , GET , /que.php , pb"},{"field":"sample.tasks.http","operator":"contains","value":"iholpforyou4.com , GET , /d_index.php , pb"},{"field":"sample.tasks.http","operator":"contains","value":"iholpforyou4.com , GET , /d_index.php , pb"},{"field":"sample.tasks.http","operator":"contains","value":"80.78.242.47 , GET , /pointer.php?proxy=217.172.179.88%3A14452&secret=BER5w4evtjszw4MBRW , "},{"field":"sample.tasks.http","operator":"contains","value":"markovqwesta.com , GET , /que.php , pb"},{"field":"sample.tasks.mutex","operator":"contains","value":"winlsm.exe , CreateMutexW , Local\\!IETld!Mutex"},{"field":"sample.tasks.mutex","operator":"contains","value":"winlsm.exe , CreateMutexW , IESQMMUTEX_0_208"},{"field":"sample.tasks.mutex","operator":"contains","value":"smss-mon.exe , CreateMutexW , c:!documents and settings!administrator!local settings!temporary internet files!content.ie5!"},{"field":"sample.tasks.mutex","operator":"contains","value":"smss-mon.exe , CreateMutexW , c:!documents and settings!administrator!cookies!"},{"field":"sample.tasks.mutex","operator":"contains","value":"smss-mon.exe , CreateMutexW , c:!documents and settings!administrator!local settings!history!history.ie5!"},{"field":"sample.tasks.mutex","operator":"contains","value":"smss-mon.exe , CreateMutexW , WininetConnectionMutex"},{"field":"sample.tasks.mutex","operator":"contains","value":"smss-mon.exe , CreateMutexW , "}]} 795 | ``` 796 | 797 | You can also send more complex AF queries by passing the "query" value to the *-i* flag. Below we run the "uniq_sessions" function to look at session data for all samples tagged with Locky betwen March 15th-18th that were delivered via web-browsing. 798 | 799 | ##### complex_query 800 | 801 | ``` 802 | python af_lenz.py -i query -q '{"operator":"all","children":[{"field":"sample.tag","operator":"is in the list","value":["Unit42.Locky"]},{"field":"sample.create_date","operator":"is in the range","value":["2016-03-15T00:00:00","2016-03-18T23:59:59"]},{"field":"session.app","operator":"is","value":"web-browsing"}]}' -r uniq_sessions 803 | 804 | {"operator":"all","children":[{"field":"sample.tag","operator":"is in the list","value":["Unit42.Locky"]},{"field":"sample.create_date","operator":"is in the range","value":["2016-03-15T00:00:00","2016-03-18T23:59:59"]},{"field":"session.app","operator":"is","value":"web-browsing"}]} 805 | 806 | [+] filename [+] 807 | 808 | dh32f 809 | wqi3pd 810 | nc4f6gf 811 | kjshdf4dj 812 | 813 | 9c09891883e4170fe92321700ef42c4f 814 | ce31e5c123842708522c5b8330481345 815 | a02f352bb0f1e0513a7c9cc8428f353b 816 | sample 817 | 818 | [+] application [+] 819 | 820 | web-browsing 821 | 822 | [+] country [+] 823 | 824 | Spain 825 | South Africa 826 | Mexico 827 | United States 828 | 829 | Netherlands 830 | Austria 831 | Colombia 832 | Korea Republic Of 833 | 834 | [+] industry [+] 835 | 836 | High Tech 837 | Utilities 838 | Higher Education 839 | Professional and Legal Services 840 | 841 | Hospitality 842 | Other 843 | Automotive 844 | Energy 845 | 846 | [+] processed 1851 sessions [+] 847 | ``` 848 | 849 | You're also not limited to just PE files, but can run the functions on the other analyzers used in AutoFocus. 850 | 851 | ##### apk_analyzer 852 | 853 | ``` 854 | python af_lenz.py -i query -q '{"operator":"any","children":[{"field":"sample.tasks.apk_embeded_url","operator":"contains","value":"smali/com/simplelocker"},{"field":"sample.tasks.apk_suspicious_api_call","operator":"contains","value":"smali/com/simplelocker"}]}' -r common_artifacts -c 70 855 | 856 | {"operator":"any","children":[{"field":"sample.tasks.apk_embeded_url","operator":"contains","value":"smali/com/simplelocker"},{"field":"sample.tasks.apk_suspicious_api_call","operator":"contains","value":"smali/com/simplelocker"}]} 857 | 858 | [+] hashes [+] 859 | 860 | 92c7a01800b9eaf29c3f3808dc1b1285a2a452c2ce87888daa9cba6dedfbbb61 861 | 304efc1f0b5b8c6c711c03a13d5d8b90755cec00cac1218a7a4a22b091ffb30b 862 | 9f05372f74ddb9949f8b260ca360335651ae8282bfa615a29cd448e01667ca06 863 | bd69ea8206070cf4db9b10a07a85412cf6be85d84d905a6b16c0bda61bbe8b55 864 | 865 | 0a56882c6ae7e211e4cf3b222d8ece2b1b744ef6abb219167a834c3569e7cca8 866 | 88881454cee58f8ecbf33a5e0875ba03ceb8e3ca2660421fb986b1bb67cedd87 867 | a71073af1c81263e90d5ec0e6ac8b3f9480ebcb0c42bc8f49be5e6c99c069bc5 868 | 2db3f60e5b8f60bc28404e2550103c9a6fb9b8f7cb5803017ad8a5cf37f1d1f8 869 | 870 | [+] apk_misc [+] 871 | 872 | com.simplelocker.DeviceAdminChecker 873 | com.simplelocker.Main 874 | 875 | [+] apk_filter [+] 876 | 877 | android.intent.action.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE 878 | 879 | [+] apk_receiver [+] 880 | 881 | com.simplelocker.MessageReceiver 882 | com.simplelocker.SDCardServiceStarter 883 | com.simplelocker.MyDeviceAdminReceiver 884 | com.simplelocker.ServiceStarter 885 | 886 | [+] apk_service [+] 887 | 888 | com.simplelocker.CheckService 889 | com.simplelocker.MainService 890 | com.simplelocker.DecryptService 891 | 892 | [+] processed 24 hashes with a BGM filter of 10000 [+] 893 | ``` 894 | 895 | Additionally, you can also limit the number of samples to analyze - running the above query again but limiting to 5 results. 896 | 897 | ##### limit_result 898 | 899 | ``` 900 | python af_lenz.py -i query -q '{"operator":"any","children":[{"field":"sample.tasks.apk_embeded_url","operator":"contains","value":"smali/com/simplelocker"},{"field":"sample.tasks.apk_suspicious_api_call","operator":"contains","value":"smali/com/simplelocker"}]}' -r common_artifacts -c 70 -l 5 901 | 902 | {"operator":"any","children":[{"field":"sample.tasks.apk_embeded_url","operator":"contains","value":"smali/com/simplelocker"},{"field":"sample.tasks.apk_suspicious_api_call","operator":"contains","value":"smali/com/simplelocker"}]} 903 | 904 | [+] hashes [+] 905 | 906 | 5e650b16b6565d66d3c4ae0800b89cc4942d57d6324b2bfa41b3a331cbdc2659 907 | c9335985a3f04611c155528827b38447f549307997715a015acc73a396d7c2b7 908 | 88881454cee58f8ecbf33a5e0875ba03ceb8e3ca2660421fb986b1bb67cedd87 909 | a71073af1c81263e90d5ec0e6ac8b3f9480ebcb0c42bc8f49be5e6c99c069bc5 910 | 304efc1f0b5b8c6c711c03a13d5d8b90755cec00cac1218a7a4a22b091ffb30b 911 | 912 | [+] connection [+] 913 | 914 | unknown , tcp , 89.144.14.29:80 , DE 915 | 916 | [+] apk_misc [+] 917 | 918 | com.simplelocker.DeviceAdminChecker 919 | com.simplelocker.Main 920 | 921 | [+] apk_filter [+] 922 | 923 | android.intent.action.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE 924 | 925 | [+] apk_receiver [+] 926 | 927 | com.simplelocker.ServiceStarter 928 | com.simplelocker.SDCardServiceStarter 929 | com.simplelocker.MyDeviceAdminReceiver 930 | com.simplelocker.MessageReceiver 931 | 932 | [+] apk_service [+] 933 | 934 | com.simplelocker.MainService 935 | com.simplelocker.CheckService 936 | com.simplelocker.DecryptService 937 | 938 | [+] processed 5 hashes with a BGM filter of 10000 [+] 939 | ``` 940 | 941 | The next function is more for quick meta-data analysis, but "sample_info" will return the hash, file type, create date, verdict, file size, and tags that associate to samples from a query. These are pipe delimeted for quick parsing. 942 | 943 | ##### meta_data 944 | 945 | ``` 946 | python af_lenz.py -i file -q "VAULT.KEY" -r meta_scrape -l 10 947 | 948 | {"operator":"all","children":[{"field":"sample.tasks.file","operator":"contains","value":"VAULT.KEY"}]} 949 | 950 | [+] sample_meta [+] 951 | 952 | 97e0dd5032bd0dc4877ed62ba01135644f867029aa23de71cec0eb8cd91a3ad1 | PE | 2016-09-09 09:24:02 | malware | 220992 | Unit42.DeleteVolumeSnapshots,Unit42.ModifyBootConfig,Commodity.Pony 953 | ab2773c8ca1de56de2c2cb15801a6de57217194a4443d3538bd7bb3434e9f380 | PE | 2016-09-16 00:36:32 | malware | 204800 | Unit42.RansomCrypt,Unit42.DeleteVolumeSnapshots,Unit42.ProcessHollowing,Unit42.ModifyBootConfig,Commodity.Pony 954 | da3ebf8fd9f992e8edce27bdbe370e77e7c2981028762696058c6d4db8a5439d | PE | 2016-09-09 09:24:01 | malware | 180224 | Unit42.RansomCrypt,Unit42.DeleteVolumeSnapshots,Unit42.ModifyBootConfig,Unit42.ProcessHollowing,Commodity.Pony 955 | 52aeb37b72aae57c23f5e007af56c32ee26ae814a2507440c6c805c948643fcc | PE | 2016-09-12 23:54:57 | malware | 224150 | Unit42.RansomCrypt,Unit42.DeleteVolumeSnapshots,Unit42.ModifyBootConfig,Commodity.Pony 956 | 28208e03c4a1d9bb71bc4fc97fe78c7eeee11bc74be18bbb69d6f13b6f74ea20 | PE | 2016-09-13 00:18:07 | malware | 222921 | Unit42.RansomCrypt,Unit42.DeleteVolumeSnapshots,Unit42.ModifyBootConfig,Commodity.Pony 957 | 58c834338eee25fc44f1c4178feb446c1a1dd433094d4bad211d6d255de25993 | PE | 2016-09-14 01:04:36 | malware | 122918 | Unit42.RansomCrypt,Unit42.DeleteVolumeSnapshots,Unit42.ProcessHollowing,Unit42.ModifyBootConfig,Commodity.Pony 958 | 395eec01a2a71c36d461c2e84b3707b3c03375bfbea3618bc50c540fd5323884 | PE | 2016-09-15 09:23:41 | malware | 224152 | Unit42.RansomCrypt,Unit42.DeleteVolumeSnapshots,Unit42.ModifyBootConfig,Commodity.Pony 959 | 0c7167d0ea4a6e997f92d43ecdbbb8063f12f906b0fcb71801182df18629d2ea | PE | 2016-09-12 09:39:34 | malware | 220638 | Unit42.RansomCrypt,Unit42.DeleteVolumeSnapshots,Unit42.ModifyBootConfig,Commodity.Pony 960 | 330877e342fe05bc7c6260315c1e812d19242bf523df1c6528fe7148f42ca991 | PE | 2016-09-13 00:13:02 | malware | 221492 | Unit42.RansomCrypt,Unit42.DeleteVolumeSnapshots,Unit42.ModifyBootConfig,Commodity.Pony 961 | 13f2864d4ab5cdc900f6cca9d031bdc2cfa91b764920b722d60d54462e61d4da | PE | 2016-09-15 17:30:15 | malware | 222921 | Unit42.RansomCrypt,Unit42.DeleteVolumeSnapshots,Unit42.ModifyBootConfig,Commodity.Pony 962 | 963 | [+] processed 10 samples [+] 964 | ``` 965 | 966 | ##### extract_all 967 | 968 | You can also use the "hash_scrape" function to pull back ALL data across a set of samples or leverage the "common_artifacts" function, with a commonality of 0% (meaning everything is a match), to take advantage of outputs like "range". For example, if you want to know every unique Process entry across a sample set for further analysis and know how common each one is. 969 | 970 | ``` 971 | python af_lenz.py -i dns -q markovqwesta.com -r common_artifacts -c 0 -o process -s range 972 | 973 | {"operator":"all","children":[{"field":"alias.domain","operator":"contains","value":"markovqwesta.com"}]} 974 | 975 | [+] hashes [+] 976 | 977 | 639d03fb6465a94189fb5b29887afe0965a95c9a7778fb624b92eef6ed22b7bb 978 | c19487136ebc82a38e13264ca8bd1b7983039db103d2520c52e49f40ac35b1db 979 | 232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d 980 | 1963a881beefd720648ca9a28c578b4f10f6ea38a8dfab436756fd64dc418bc3 981 | 982 | cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03 983 | 23e9815fe25321b0349e8c6fc22473914a306d27a9d8cae2872396cf7a14c099 984 | ffe9fb1f9ef7465c99edfe17ccce496172cba47357b2caff6720900a0f6426b2 985 | c97bd3d159222bfe650647aefb92fd13b2e590f8d5dd5781110a0cf61958fc33 986 | 987 | [+] process [+] 988 | 989 | 16 | ntsystem.exe , LoadLibraryExW , hnetcfg.dll , , 0 990 | 8 | mswinlogon.exe , LoadLibraryExW , USER32.dll , , 0 991 | 8 | ntreader_sl.exe , LoadLibraryExW , ADVAPI32.dll , , 0 992 | 8 | wincsrss.exe , ZwTerminateProcess , ntwinlogon.exe , 993 | 994 | 8 | wincsrss.exe , LoadLibraryExW , Windows\SysWOW64\ieframe.dll , , 8 995 | 8 | winlogonsvc.exe , LoadLibraryExW , WININET.dll , , 0 996 | 8 | ntcsrss.exe , GetModuleHandle , documents and settings\administrator\sample.exe 997 | 8 | system-updater.exe , LoadLibraryExW , SHELL32.dll , , 0 998 | 999 | [+] processed 12 hashes with a BGM filter of 10000 [+] 1000 | ``` 1001 | 1002 | ##### quiet_flag 1003 | 1004 | The 'Q' flag can be used in combination with any other valid set of arguments to limit the output of the script. When invoked, this flag suppresses any informational output that is normally generated by the script. As a result, the only output returned by the script is returned data. This can then be manipulated easily with other tools. 1005 | 1006 | ``` 1007 | python af_lenz.py -i dns -q 'www.otrfmbluvrde.com' -r meta_scrape -o sha256,file_size,file_type -Q 1008 | 1009 | 04b770027efe3259f6ed6bba5e91fd3309ab44f74f4022efc3321576fc969da0 | 11208 | Adobe Flash File 1010 | 7ddb1610dc730cbd76786d834236a24b3b5f51fe5d14755b65b7ff8531b6806f | 11208 | Adobe Flash File 1011 | 4dfb5da4aa0e5e58d811392ea902a40d4cdecc9f1a267656d5564c341226488f | 11208 | Adobe Flash File 1012 | 80d04c67ec1699b370ddd8d9a6dacab51fa5ebcefbfb992b329cef404827da5e | 11208 | Adobe Flash File 1013 | 5562ac2ef1fa431ac34cd4c332fc269c3fbca789a8417ee6f27d5433f88a0bbd | 11208 | Adobe Flash File 1014 | ad9264b8e777ad84343633d0b972b6fecef9a7e46a151caf84019ef49ff64427 | 4304 | Adobe Flash File 1015 | ``` 1016 | 1017 | ##### diff 1018 | 1019 | The 'diff' function allows you to compare two hashes and identify the differences between each one. It will not print common-lines. 1020 | 1021 | ``` 1022 | python af_lenz.py -i dns -q 'www.otrfmbluvrde.com' -r diff -o process,mutex,dns 1023 | 1024 | {"operator":"all","children":[{"field":"alias.domain","operator":"contains","value":"www.otrfmbluvrde.com"}]} 1025 | 1026 | [+] hashes [+] 1027 | 1028 | 04b770027efe3259f6ed6bba5e91fd3309ab44f74f4022efc3321576fc969da0 1029 | 7ddb1610dc730cbd76786d834236a24b3b5f51fe5d14755b65b7ff8531b6806f 1030 | 1031 | [+] diff [+] 1032 | 1033 | < | 04b770027efe3259f6ed6bba5e91fd3309ab44f74f4022efc3321576fc969da0 1034 | > | 7ddb1610dc730cbd76786d834236a24b3b5f51fe5d14755b65b7ff8531b6806f 1035 | 1036 | [+] process [+] 1037 | 1038 | < | svchost.exe , created , , Windows\System32\taskeng.exe , taskeng.exe {F03FAC4A-6277-42B8-8C74-F5AA3E77F347} S-1-5-18:NT AUTHORITY\System:Service: 1039 | < | iexplore.exe , SetTimer , 00000001 , 00001388(original:00001388) , 726D4756 1040 | < | iexplore.exe , SetTimer , 00007feb , 00001388(original:00001388) , 726D4756 1041 | < | iexplore.exe , SetTimer , 00000000 , 00001388(original:000088b8) , 73586B52 1042 | --- 1043 | > | svchost.exe , created , , Windows\System32\taskeng.exe , taskeng.exe {CD585D04-948B-4287-9C53-FC33C8D8F4D7} S-1-5-18:NT AUTHORITY\System:Service: 1044 | > | iexplore.exe , SetTimer , 00000001 , 00001388(original:00001388) , 72AD4756 1045 | > | iexplore.exe , SetTimer , 00007feb , 00001388(original:00001388) , 72AD4756 1046 | > | iexplore.exe , SetTimer , 00000000 , 00001388(original:000088b8) , 73B06B52 1047 | 1048 | [+] mutex [+] 1049 | 1050 | < | iexplore.exe , CreateMutexW , Groove:PathMutex:wEfok5Qw0IjiXFzS91XPfTjqjes= 1051 | < | iexplore.exe , CreateMutexW , Groove:PathMutex:gWNhnwLaz/1PBZUWP+4N4Zd81LY= 1052 | --- 1053 | > | iexplore.exe , CreateMutexW , Groove:PathMutex:MFJQHMsconMFS29BQOwAYulej6k= 1054 | > | iexplore.exe , CreateMutexW , Groove:PathMutex:5/aBAYZWYsoJ3j53zcvNAGvCHSo= 1055 | 1056 | [+] dns [+] 1057 | 1058 | < | wpad.XKTMLKA99660986.local , , NXDOMAIN 1059 | --- 1060 | > | wpad.ZPCTGVR49286334.local , , NXDOMAIN 1061 | 1062 | [+] processed 2 hashes with a BGM filter of 10000 [+] 1063 | ``` 1064 | 1065 | ##### count 1066 | 1067 | The 'count' parameter can be passed to the 'special' function and will count the number of unique lines per hash across the sample set. This allows you to see how frequently something occurred. 1068 | 1069 | ``` 1070 | python af_lenz.py -i dns -q 'markovqwesta.com' -r hash_scrape -o dns -s count -l 15 1071 | 1072 | {"operator":"all","children":[{"field":"alias.domain","operator":"contains","value":"markovqwesta.com"}]} 1073 | 1074 | [+] hashes [+] 1075 | 1076 | 639d03fb6465a94189fb5b29887afe0965a95c9a7778fb624b92eef6ed22b7bb 1077 | c19487136ebc82a38e13264ca8bd1b7983039db103d2520c52e49f40ac35b1db 1078 | 232c8369c1ac8a66d52df294519298b4bcc772e7bed080c38ac141ad1928894d 1079 | 1963a881beefd720648ca9a28c578b4f10f6ea38a8dfab436756fd64dc418bc3 1080 | 1081 | cda1be0cee01aa74518c4e6eca4a4ecf8fae7ed13fa8f392d88988a5ac76ec03 1082 | 23e9815fe25321b0349e8c6fc22473914a306d27a9d8cae2872396cf7a14c099 1083 | ffe9fb1f9ef7465c99edfe17ccce496172cba47357b2caff6720900a0f6426b2 1084 | c97bd3d159222bfe650647aefb92fd13b2e590f8d5dd5781110a0cf61958fc33 1085 | 1086 | [+] dns [+] 1087 | 1088 | 9 | markovqwesta.com , 193.235.147.11 , A 1089 | 1 | iholpforyou4.com , , NXDOMAIN 1090 | 10 | markovqwesta.com , ns4.cnmsn.com , NS 1091 | 10 | markovqwesta.com , ns3.cnmsn.com , NS 1092 | 1 | iholpforyou4.com , 46.36.221.85 , A 1093 | 1 | iholpforyou4.com , ns4.cnmsn.com , NS 1094 | 1 | iholpforyou4.com , ns3.cnmsn.com , NS 1095 | 2 | exseomonstars.com , , NXDOMAIN 1096 | 2 | markovqwesta.com , , NXDOMAIN 1097 | 1 | markovqwesta.com , 176.114.3.49 , A 1098 | 1 | support.microsoft.com , 157.56.56.139 , A 1099 | 1 | www.mozilla.com , 63.245.217.20 , A 1100 | 1101 | [+] processed 12 hashes with a BGM filter of 10000 [+] 1102 | ``` 1103 | 1104 | ##### tag_count 1105 | 1106 | The 'tag_count' parameter can be passed to the special function to count the raw number of tags per sample. This allows you to quickly take a large set of samples and boil up tags with large coverage. 1107 | 1108 | ``` 1109 | python af_lenz.py -i tag -q 'Unit42.AlphaCrypt' -r meta_scrape -s tag_count 1110 | 1111 | {"operator":"all","children":[{"field":"sample.tag","operator":"is in the list","value":["Unit42.AlphaCrypt"]}]} 1112 | 1113 | [+] sample_meta [+] 1114 | 1115 | 72 | Unit42.IPAddressLookup 1116 | 1 | Unit42.ModifyWindowsFirewall 1117 | 72 | Unit42.TeslaCrypt 1118 | 1 | Commodity.Virut 1119 | 60 | Unit42.ProcessHollowing 1120 | 72 | Unit42.AlphaCrypt 1121 | 72 | Unit42.DeleteVolumeSnapshots 1122 | 1123 | [+] processed 72 samples [+] 1124 | ``` 1125 | 1126 | ##### suspect_artifacts 1127 | 1128 | The 'suspicious' and 'highly_suspicious' parameters can be passed to the filter function to use the AutoFocus definitions to filter artifacts. These are presented in AF as red and yellow exclamation points. 1129 | 1130 | ``` 1131 | $ python af_lenz.py -i tag -q 'Unit42.AlphaCrypt' -r hash_scrape -l 1 -f highly_suspicious -o dns,mutex 1132 | 1133 | {"operator":"all","children":[{"field":"sample.tag","operator":"is in the list","value":["Unit42.AlphaCrypt"]}]} 1134 | 1135 | [+] hashes [+] 1136 | 1137 | 0ca8a7f1c443af649230f95ab18638e0e1238d74d6ab0efe0d14b883ae7bd592 1138 | 1139 | [+] dns [+] 1140 | 1141 | wpad.ZPAN28185489917.local , , NXDOMAIN 1142 | dpckd2ftmf7lelsa.tor2web.blutmagie.de.FYRNF5563656069.local , , NXDOMAIN 1143 | dpckd2ftmf7lelsa.9isernvur33.com.FYRNF5563656069.local , , NXDOMAIN 1144 | dpckd2ftmf7lelsa.9isernvur33.com , , NXDOMAIN 1145 | dpckd2ftmf7lelsa.afnwdsy4j32.com.FYRNF5563656069.local , , NXDOMAIN 1146 | dpckd2ftmf7lelsa.afnwdsy4j32.com , , NXDOMAIN 1147 | dpckd2ftmf7lelsa.tor2web.org , 38.229.70.4 , A 1148 | dpckd2ftmf7lelsa.tor2web.blutmagie.de , , NXDOMAIN 1149 | 1150 | [+] mutex [+] 1151 | 1152 | gdgvaux.exe , CreateMutexW , VideoRenderer 1153 | gdgvaux.exe , CreateMutexW , 1154 | gdgvaux.exe , CreateMutexW , safsdfasdfwrtqr15 1155 | gdgvaux.exe , CreateMutexW , c:!docume~1!admini~1!locals~1!temp!temporary internet files!content.ie5! 1156 | gdgvaux.exe , CreateMutexW , c:!docume~1!admini~1!locals~1!temp!cookies! 1157 | gdgvaux.exe , CreateMutexW , c:!docume~1!admini~1!locals~1!temp!history!history.ie5! 1158 | 1159 | [+] processed 1 hashes with a BGM filter of highly_suspicious [+] 1160 | ``` 1161 | 1162 | The same hash with a different filter, resulting in different data. 1163 | 1164 | ``` 1165 | $ python af_lenz.py -i tag -q 'Unit42.AlphaCrypt' -r hash_scrape -l 1 -f suspicious -o dns,mutex 1166 | 1167 | {"operator":"all","children":[{"field":"sample.tag","operator":"is in the list","value":["Unit42.AlphaCrypt"]}]} 1168 | 1169 | [+] hashes [+] 1170 | 1171 | 0ca8a7f1c443af649230f95ab18638e0e1238d74d6ab0efe0d14b883ae7bd592 1172 | 1173 | [+] dns [+] 1174 | 1175 | tor2web.org , ns1.dnsimple.com , NS 1176 | ilo.brenz.pl , 148.81.111.121 , A 1177 | ipinfo.io , ns-595.awsdns-10.net , NS 1178 | 1179 | [+] mutex [+] 1180 | 1181 | winlogon.exe , CreateMutexW , c:!documents and settings!administrator!local settings!temporary internet files!content.ie5! 1182 | winlogon.exe , CreateMutexW , c:!documents and settings!administrator!cookies! 1183 | winlogon.exe , CreateMutexW , c:!documents and settings!administrator!local settings!history!history.ie5! 1184 | winlogon.exe , CreateMutexW , WininetConnectionMutex 1185 | winlogon.exe , CreateMutexW , 1186 | sample.exe , CreateMutexW , VideoRenderer 1187 | winlogon.exe , CreateMutexW , Global\WindowsUpdateTracingMutex 1188 | 1189 | [+] processed 1 hashes with a BGM filter of suspicious [+] 1190 | ``` 1191 | 1192 | ##### service_scrape 1193 | 1194 | Scrape the unique service names out of a set of samples. 1195 | 1196 | ``` 1197 | $ python af_lenz.py -i query -q '{"operator":"all","children":[{"field":"sample.tasks.service","operator":"has any value","value":""},{"field":"sample.malware","operator":"is","value":1},{"field":"sample.tasks.dns","operator":"has any value","value":""}]}' -r service_scrape -l 10 1198 | 1199 | {"operator":"all","children":[{"field":"sample.tasks.service","operator":"has any value","value":""},{"field":"sample.malware","operator":"is","value":1},{"field":"sample.tasks.dns","operator":"has any value","value":""}]} 1200 | 1201 | [+] hashes [+] 1202 | 1203 | e902f59fedc8b13e87baa33c7ad7a13401653b7340c2501e4a314048333b5215 1204 | 51c62ee5e38f111928b45585d7c70ba91f973b664fa74d390661b5007130758d 1205 | 4859a2938f3af469274f6b98747f7cbff579eeea754b88d481e7c1a44c320136 1206 | dd7f0b9edadbbda92ab73430536d1d36e8641170a664767fe2316fd7454f6a5e 1207 | 1208 | ef9e299d56d9ce67d5c4b472d52310cc3a28c3aaf169c60a02c97dd96bd3d323 1209 | 66bc9c714e69c54f8757dab13c2924d5257141ae77867d127ba5053ba5f283ef 1210 | 5cbea737b5f88e16bbc96952eb1310a3fa0c51f25a81a5212f5b01ebe6c4eb5f 1211 | 5fa6d5012d2df74a22536f8a7a4c240bb36464873ff62b4dcaed8bedea2bcb2e 1212 | 1213 | [+] service [+] 1214 | 1215 | SysCPRC 1216 | QMgcIwoT 1217 | 1218 | [+] processed 10 hashes with a BGM filter of 10000 [+] 1219 | ``` 1220 | 1221 | ##### write_out 1222 | 1223 | The "-w" flag can be used to specify that STDOUT be redirected to a file. 1224 | 1225 | ``` 1226 | $ python af_lenz.py -i query -q '{"operator":"all","children":[{"field":"sample.tasks.service","operator":"has any value","value":""},{"field":"sample.malware","operator":"is","value":1},{"field":"sample.tasks.dns","operator":"has any value","value":""}]}' -r service_scrape -l 10 -w aflenz.txt -Q 1227 | 1228 | $ cat aflenz.txt 1229 | SysCPRC 1230 | QMgcIwoT 1231 | ``` 1232 | 1233 | ##### bgm_value 1234 | 1235 | The "bgm" parameter can be passed to the "-s" flag to print the B(enign), G(rayware), and M(alware) counts for each associated artifact. 1236 | 1237 | ``` 1238 | $ python af_lenz.py -i dns -q "markovqwesta.com" -l 1 -r hash_scrape -s bgm -f 0 -o process 1239 | 1240 | {"operator":"all","children":[{"field":"alias.domain","operator":"contains","value":"markovqwesta.com"}]} 1241 | 1242 | [+] hashes [+] 1243 | 1244 | 8ef7212841ca0894232c2d118905dfacdcad16d00ca545745eff7123565b5b39 1245 | 1246 | [+] process [+] 1247 | 1248 | 19.7K 9.9M 82.1K | svchost.exe , created , , Users\Administrator\sample.exe , Users\Administrator\sample.exe 1249 | 0 0 1 | sample.exe , created , , Users\Administrator\AppData\Local\FWJ\csrss_patcher.exe , Users\sciZnBNl6e0Mg\AppData\Local\FWJ\csrss_patcher.exe 1250 | 15.0K 6.0M 65.0K | svchost.exe , terminated , , Users\Administrator\sample.exe 1251 | 0 0 1 | csrss_patcher.exe , created , , Users\Administrator\AppData\Local\FWJ\syswinlogon.exe , Users\sciZnBNl6e0Mg\AppData\Local\FWJ\syswinlogon.exe 1252 | 0 0 1 | csrss_patcher.exe , created , , Windows\SysWOW64\netsh.exe , Windows\System32\netsh.exe firewall add allowedprogram Users\sciZnBNl6e0Mg\AppData\Local\FWJ\csrss_patcher.exe Windows Microsoft .NET Framework NGEN v4.0.30319_X64 ENABLE 1253 | 337M 9.6M 79.5K | csrss.exe , created , , Windows\System32\conhost.exe , \??\Windows\system32\conhost.exe 1254 | 0 0 1 | csrss_patcher.exe , terminated , , Windows\SysWOW64\netsh.exe 1255 | 370M 7.9M 87.6K | winlogon.exe , terminated , , Windows\System32\userinit.exe 1256 | 85.3K 764K 15.7K | svchost.exe , terminated , , Windows\System32\mobsync.exe 1257 | 354M 10.4K 87.6K | unknown , terminated , , Program Files (x86)\Adobe\Reader 11.0\Reader\reader_sl.exe 1258 | 376M 11.6K 92.8K | SearchIndexer.exe , terminated , , Windows\System32\SearchProtocolHost.exe 1259 | 376M 11.6K 92.9K | cmd.exe , terminated , , Users\Administrator\explorer.exe 1260 | 393M 11.7K 93.4K | explorer.exe , terminated , , WINDOWS\system32\cmd.exe 1261 | ``` 1262 | 1263 | ##### tag_info 1264 | 1265 | Returns the basic tag meta-data. 1266 | 1267 | ``` 1268 | $ python af_lenz.py -i tag -q 'Unit42.NJRat' -r tag_info 1269 | 1270 | {"operator":"all","children":[{"field":"sample.tag","operator":"is in the list","value":["Unit42.NJRat"]}]} 1271 | 1272 | [+] Tag Info [+] 1273 | 1274 | Tag Name : NJRat 1275 | Tag Public Name : Unit42.NJRat 1276 | Tag Count : 1225435 1277 | Tag Created : 2016-01-01 00:00:00 1278 | Tag Last Hit : 2018-07-19 10:08:08 1279 | Tag Class : malware_family 1280 | Tag Status : enabled 1281 | Tag Source : unit42 1282 | Tag Description : NJRat is a remote-access Trojan that has been used for the last few years. We haven’t heard much about NJRat since April 2014, but some samples we’ve recently received show that this malware is making a comeback. ( For some background on NJRat, a 2013 report from Fidelis Cybersecurity Solutions at General Dynamics detailed indicators, domains, and TTP’s in conjunction with cyber-attacks using NJRat.) 1283 | Tag References : 1284 | [Source] Fidelis | [Title] NJRat Uncovered | [URL] http://www.fidelissecurity.com/sites/default/files/FTA_1009-njRAT_Uncovered_rev2.pdf 1285 | [Source] Phishme | [Title] Return of NJRat | [URL] http://phishme.com/the-return-of-njrat/ 1286 | Tag Queries : 1287 | 1288 | {"operator":"all","children":[{"field":"sample.tasks.process","operator":"contains","value":"\\DR-RAT\\Server.exe"}]} 1289 | 1290 | {"operator":"any","children":[{"field":"sample.tasks.file","operator":"contains","value":"users\\administrator\\appdata\\roaming\\windowsupdate.config"},{"field":"sample.tasks.mutex","operator":"contains","value":"CreateMutexW , f91bafdfdfd375ea2dff161183a71733"}]} 1291 | 1292 | 1293 | ``` 1294 | 1295 | ##### tag_check 1296 | 1297 | ``` 1298 | $ python af_lenz.py -i tag -q 'Commodity.NJRat,2ea576290117ca82cb55f599e00233eb963d940f96ed05f5ce31e7262573e212' -r tag_check 1299 | 1300 | Tag: Commodity.NJRat 1301 | Hash: 2ea576290117ca82cb55f599e00233eb963d940f96ed05f5ce31e7262573e212 1302 | 1303 | [+] Matched Query [+] 1304 | 1305 | {u'operator': u'contains', u'field': u'sample.tasks.registry', u'value': u'SetValueKey , HKCU\\Environment\\SEE_MASK_NOZONECHECKS , Value:1 , Type:1'} 1306 | 1307 | [+] registry [+] 1308 | 1309 | sample.exe , SetValueKey , HKCU\Environment\SEE_MASK_NOZONECHECKS , Value:1 , Type:1 1310 | 1311 | [+] Matched Query [+] 1312 | 1313 | {u'operator': u'contains', u'field': u'sample.tasks.registry', u'value': u'HKCU\\Environment , SEE_MASK_NOZONECHECKS , 1'} 1314 | 1315 | [+] registry [+] 1316 | 1317 | sample.exe , RegSetValueEx , HKCU\Environment , SEE_MASK_NOZONECHECKS , 1 1318 | 1319 | [+] processed 1 hashes with a BGM filter of 0 [+] 1320 | ``` 1321 | 1322 | ##### coverage_scrape 1323 | 1324 | ``` 1325 | $ python af_lenz.py -i hash -q '9b9c1720afd80b32f800ed7183447d9139e7a614bdcc48bafbe9e3e36bed992b' -r coverage_scrape 1326 | 1327 | {"operator":"all","children":[{"field":"sample.sha256","operator":"is","value":"9b9c1720afd80b32f800ed7183447d9139e7a614bdcc48bafbe9e3e36bed992b"}]} 1328 | 1329 | [+] hashes [+] 1330 | 1331 | 9b9c1720afd80b32f800ed7183447d9139e7a614bdcc48bafbe9e3e36bed992b 1332 | 1333 | [+] dns_sig [+] 1334 | 1335 | ocsp.comodoca.com , generic:ocsp.comodoca.com , 2013-10-06 01:23:35 , None , None , False 1336 | 1337 | [+] url_cat [+] 1338 | 1339 | ns1.comododns.com , Business and Economy 1340 | ns0.comododns.net , Computer and Internet Info 1341 | comodoca.com , Computer and Internet Info 1342 | crl.comodoca.com , Computer and Internet Info 1343 | crl.usertrust.com , Computer and Internet Info 1344 | crt.comodoca.com , Computer and Internet Info 1345 | dns.msftncsi.com , Computer and Internet Info 1346 | www.download.windowsupdate.com , Computer and Internet Info 1347 | msftncsi.com , Computer and Internet Info 1348 | cloudflare.net , Computer and Internet Info 1349 | usertrust.com , Computer and Internet Info 1350 | ns1.comododns.net , Computer and Internet Info 1351 | ns4.cloudflare.net , Computer and Internet Info 1352 | ns4.msft.net , Computer and Internet Info 1353 | ns5.cloudflare.net , Computer and Internet Info 1354 | ocsp.comodoca.com , Computer and Internet Info 1355 | ocsp.usertrust.com , Computer and Internet Info 1356 | time.windows.com , Computer and Internet Info 1357 | n5dspw65.akamai.net , Content Delivery Networks 1358 | akadns.net , Content Delivery Networks 1359 | dspw65.akamai.net , Content Delivery Networks 1360 | a0dspw65.akamai.net , Content Delivery Networks 1361 | a12-131.akadns.org , Content Delivery Networks 1362 | a1-128.akadns.net , Content Delivery Networks 1363 | klifex.pw , Home and Garden 1364 | unknown , Society 1365 | 1366 | [+] wf_av_sig [+] 1367 | 1368 | Dapato.dr/Win32.fui.g , 2018-01-20 05:05:48 , 2499 , 2499 , True 1369 | 1370 | [+] processed 1 hashes with a BGM filter of 10000 [+] 1371 | ``` 1372 | 1373 | ### [+] CHANGE LOG [+] 1374 | 1375 | v1.3.6 - 08JUL2019 1376 | * Bug fixes related to new "all_apk" and "all_elf" sections that got added. 1377 | 1378 | v1.3.3 - 26JUN2019 1379 | * Added filter "all_suspicious" which combines both "highly_suspicious" and "suspicious" filters into one. 1380 | * Added output "all_apk" and "all_elf" which will specify all of the respective fields for those file types. 1381 | 1382 | v1.3.2 - 17JUN2019 1383 | * Bug fixes 1384 | 1385 | v1.3.0 - 06JUN2019 1386 | * Modified the implementation of multiprocessing to allow for graceful handling of SIGINT (CTRL-C). 1387 | * Converted to Python3. 1388 | 1389 | v1.2.6 - 16MAY2018 1390 | * Added section "upload_source" for session output. 1391 | * Expanded special "count" function to work across meta and session sections. 1392 | * Updated LenzNameSpace to include platform coverage. 1393 | * Added references and tag source to the "tag_info" function. 1394 | * Fixed an issue where multiprocess module would hang due to a suspected race condition. 1395 | 1396 | v1.2.5 - 04APR2018 1397 | * Added a try/except to catch tag_check queries the script is unable to parse correctly. 1398 | * Added new output sections for ELF and Macro: elf_commands, elf_file_paths, elf_suspicious_behavior, elf_functions, elf_ip_address, elf_domains, elf_urls, macro 1399 | * Added new function "coverage_scrape" which will build strings from the data found on the Coverage tab in AF. 1400 | * Added ability to restrict output by specific VM platform with "-p" or "--platform". 1401 | 1402 | v1.2.4 - 04JAN2018 1403 | * Added tag definition (queries) to "tag_info" output. 1404 | * Fixed an issue with the filter function not working due to being a string instead of int. 1405 | * Fixed an issue with af_import handling side-by-side double quotes in strings. 1406 | * Added some new input queries - "email", "url" (alias version), and "threat". 1407 | * Added some additional logging to tag_check for troubleshooting. 1408 | * Added new "dropped_files" function (by Tom Lancaster). This will attempt to identify dropped files in the artifacts from the Process and Files sections. 1409 | 1410 | v1.2.3 - 23AUG2017 1411 | * Added some additional forced encoding within the tag_check function - should make it more stable when dealing with tag queries that have non-UTF-8 chars. 1412 | * Added AFLenz class to support third party scripts calling functions directly. 1413 | * Added the following new Session sections: session_id, dst_is_private_ip, is_uploaded, sha256, src_is_private_ip, user_id, _vsys. 1414 | * Modified to APK section "apk_name" to "apk_app_name" to align to the client library. 1415 | * Separated out the "session_scrape" and "meta_scrape" code into unique functions for calling by the AFLenz class. 1416 | * Changed "input_file_query" to "file_query" and "input_file" to "file_hashes". 1417 | 1418 | v1.2.2 - 30MAY2017 1419 | * Adjusted tag_check function to support more variations in queries. 1420 | 1421 | v1.2.1 - 05APR2017 1422 | * Changed internal hash_lookup section to only pull the requested sections as opposed to all - should save bandwidth and speed things up for queries with thousands of requests. 1423 | * Adjusted tag_check function to account for escaping double quotes within a query being checked. 1424 | 1425 | v1.2.0 - 22FEB2017 1426 | * Fixed an issue with 'count' function processing more than the expected sections. 1427 | * Added the following new APK sections: apk_app_icon, apk_cert_file, apk_defined_activity, apk_defined_intent_filter, apk_digital_signer, apk_embedded_library, apk_isrepackaged, apk_name, apk_packagename, apk_suspicious_action_monitored, apk_suspicious_file, apk_suspicious_pattern, apk_version_num. 1428 | * Added the following new MAC sections: mac_embedded_file, mac_embedded_url. 1429 | * Added the following new Session sections: device_country_code, device_country, device_hostname, business_line, device_model, device_serial, device_version, dst_country_code, dst_ip, dst_port, email_charset, src_country_code, src_country, src_ip, src_port, timestamp. 1430 | * Renamed the following sections to align with client library: apk_receiver => apk_defined_receiver, apk_sensor => apk_defined_sensor, apk_service => apk_defined_service, apk_embedurl => apk_embeded_url, apk_file => apk_internal_file, apk_permission => apk_requested_permission, apk_sensitiveapi => apk_sensitive_api_call, apk_suspiciousapi => apk_suspisicous_api_call, apk_string => apk_suspicious_string, behavior_desc => behavior. 1431 | * Added new "bgm" parameter to "-s" flag which will print the B(enign), G(rayware), and M(alware) counts per artifact. 1432 | * Added "tag_info" function which returns the tag meta-data. 1433 | * Added "tag_check" function, which should be considered BEST EFFORT. It will take each defined query for a tag and attempt to identify which sub-queries caused the sample to be tagged. Supports "contains", "is", "in the list", "regexp", and "proximity" but highly complex rules may cause problems. 1434 | 1435 | v1.1.9 - 21DEC2016 1436 | * Added "service_scrape" function to extract unique service names from a set of samples. 1437 | * Added "-w" flag so that STDOUT can be redirected to a file. 1438 | * Centralized all print operations into a new function and enabled utf-8 encoding. This should address problems with encoding errors that pop up infrequently with session data. 1439 | * Passing "0" to the "filter" or "limit" function will now cause it to default to 1 billion, thus practically negating filtering or session/sample limits. 1440 | 1441 | v1.1.8 - 15NOV2016 1442 | * Added "input_file_query" as input so Windows users can directly load queries from a file and avoid quote escaping from CLI. 1443 | * Added "suspicious" and "highly_suspicious" options to "filter" function that use pre-defined filtering templates for malware artifacts. More information can be found in the AF documentation: https://www.paloaltonetworks.com/documentation/autofocus/autofocus/autofocus_admin_guide/get-started-with-autofocus/autofocus-concepts 1444 | * Suspicious artifacts have been widely-detected across large numbers of samples. Are most frequently detected with malware. Although suspicious artifacts can be detected with grayware and benign samples, they are more often found with malware. 1445 | * The "suspicious" filter displays artifacts with a malware count 3 times larger than benign with a total malware count greater than 500. 1446 | * Highly Suspicious artifacts have been detected in very few samples. The lack of distribution of these types of artifacts could indicate an attack crafted to target a specific organization. Are most frequently detected with malware. In some cases, these artifacts have been exclusively seen with malware and never with grayware or benign samples. 1447 | * The "highly_suspicious" filter displays artifacts with a malware count 3 times larger than benign but has a total malware less than 500. 1448 | * Added "tag_count" value to special parameter. This will count each individual tag across a set of samples. 1449 | 1450 | v1.1.7 - 11OCT2016 1451 | * Added "diff" function to identify differences between two samples. 1452 | * Added "count" value to special parameter. This works on hash_scrape/uniq_sessions functions and returns count of each line across sample sets. 1453 | * Added Java API ("japi") and Behavior Description ("behavior_desc") sections. 1454 | * Cleaned up code to make adding new sections straight-forward (1 location vs multiple) and fixed logic issue for "behavior_type" section. 1455 | * Modified print functions to use auto-adjusting columns. 1456 | 1457 | v1.1.6 - 16SEP2016 1458 | * Added auto-adjusting columns for meta and session scraped output. 1459 | * Added "timestamp" to session output modifiers. 1460 | 1461 | v1.1.5 - 01AUG2016 1462 | * Added support for reading SHA256 hashes from a flat file. 1463 | 1464 | v1.1.4 - 28JUL2016 1465 | * Added support for a quiet flag. This flag suppresses the extra output of the script so as to make the returned data easier to process with other utilities. 1466 | 1467 | v1.1.3 - 20JUL2016 1468 | * Flushed out the session scrape outputs to now include src_country, dst_country, src_ip, dst_ip, src_port, dst_port. 1469 | 1470 | v1.1.2 - 13JUL2016 1471 | * Added new function, "session_scrape", which acts similar to meta_scrape except for session data. 1472 | 1473 | v1.1.1 - 12JUL2016 1474 | * Switched from threading to multiprocessing to improve speed. 1475 | * Switched from using passed arguments directly to allow for more flexibility in future updates. 1476 | 1477 | v1.1.0 - 08JUN2016 1478 | * Added ability to specify meta_scrape sections "sha256", "sha1", "md5" "file_type", "create_date", "verdict", "file_size", "tags", "ssdeep", "imphash", and "digital_signer". 1479 | * Added "imphash" and "digital_signer" to existing section lists for all sample functions. 1480 | 1481 | v1.0.9 - 18MAY2016 1482 | * Switched from "scan" to "search" for non-research enabled API keys. Add "[researcher] enabled=True" to your client library configuration file, or environment variable, to enable scan. 1483 | * Added company to session output as an option. 1484 | 1485 | v1.0.8 - 27APR2016 1486 | * Changed "hash_lookup" to "hash_scrape" and created a new function around it to support multiple hashes instead of one. 1487 | * Added query output to Yara rule generation. 1488 | * Cleaned up code for final release to public. 1489 | * Initial public release of AutoFocus Lenz. 1490 | 1491 | v1.0.7 - 21APR2016 1492 | * Fixed scrape functions not being parsed correctly for Yara rule generation. 1493 | 1494 | v1.0.6 - 18APR2016 1495 | * Added output "range" to print commonality match percents next to artifacts. 1496 | 1497 | v1.0.5 - 06APR2016 1498 | * Added function "sample_meta" to return meta data about identified samples. 1499 | 1500 | v1.0.4 - 04APR2016 1501 | * Added *-l* flag to limit the number of samples for analysis. 1502 | * Added APK sample sections for output. 1503 | * Fixed a number of logic issues. 1504 | * Cleaned up code significantly. 1505 | 1506 | v1.0.3 - 31MAR2016 1507 | * Moved to BitBucket. 1508 | * Merged updates into code. 1509 | 1510 | v1.0.2 - 22MAR2016 1511 | * Converted over to using _raw_line for everything. 1512 | 1513 | v1.0.1 - 19MAR2016 1514 | * Added "query" identifier so you can pass AF queries directly on CLI. 1515 | * Added escaping to file/registry supplied queries. 1516 | 1517 | v1.0.0 - 17MAR2016 1518 | * Initial release of af_lenz.py. 1519 | 1520 | ### [+] FUTURE TO-DOs [+] 1521 | 1522 | In no particular order... 1523 | * None 1524 | 1525 | ### [+] NOTES [+] 1526 | 1527 | If you find any issues or have requests for functionality, please contact Jeff White. 1528 | -------------------------------------------------------------------------------- /af_lenz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import asyncio 3 | import aiohttp 4 | from inspect import isfunction 5 | from autofocus import AutoFocusAPI 6 | from autofocus import AFSession, AFSample, AFTag, AFTagDefinition 7 | from collections import defaultdict 8 | 9 | from autofocus.models.analysis import _class_2_analysis_map 10 | from autofocus.models.coverage import _class_2_coverage_map 11 | from autofocus.models.analysis import _analysis_2_class_map 12 | from autofocus.models.coverage import _coverage_2_class_map 13 | 14 | from autofocus import AFSampleFactory 15 | from autofocus import AsyncRequest 16 | from autofocus.factories.analysis import AnalysisFactory as AFAnalysisFactory 17 | from autofocus.factories.coverage import CoverageFactory as AFCoverageFactory 18 | 19 | # Analysis Sections 20 | from autofocus import \ 21 | AFApiActivity, \ 22 | AFBehaviorAnalysis, \ 23 | AFBehaviorTypeAnalysis, \ 24 | AFConnectionActivity, \ 25 | AFDnsActivity, \ 26 | AFFileActivity, \ 27 | AFHttpActivity, \ 28 | AFJavaApiActivity, \ 29 | AFMutexActivity, \ 30 | AFProcessActivity, \ 31 | AFRegistryActivity, \ 32 | AFServiceActivity, \ 33 | AFUserAgentFragment 34 | # APK Specific 35 | from autofocus import \ 36 | AFAnalysisSummary, \ 37 | AFApkActivityAnalysis, \ 38 | AFApkAppName, \ 39 | AFApkCertificate, \ 40 | AFApkEmbeddedFile, \ 41 | AFApkEmbeddedLibrary, \ 42 | AFApkEmbededUrlAnalysis, \ 43 | AFApkIcon, \ 44 | AFApkIntentFilterAnalysis, \ 45 | AFApkPackage, \ 46 | AFApkReceiverAnalysis, \ 47 | AFApkRepackaged, \ 48 | AFApkRequestedPermissionAnalysis, \ 49 | AFApkSensitiveApiCallAnalysis, \ 50 | AFApkSensorAnalysis, \ 51 | AFApkServiceAnalysis, \ 52 | AFApkSuspiciousActivitySummary, \ 53 | AFApkSuspiciousApiCallAnalysis, \ 54 | AFApkSuspiciousFileAnalysis, \ 55 | AFApkSuspiciousPattern, \ 56 | AFApkSuspiciousStringAnalysis, \ 57 | AFApkVersion, \ 58 | AFDigitalSigner 59 | # MAC Specific 60 | from autofocus import \ 61 | AFMacEmbeddedFile, \ 62 | AFMacEmbeddedURL 63 | # ELF Specific 64 | from autofocus import \ 65 | AFELFCommands, \ 66 | AFELFFilePath, \ 67 | AFELFSuspiciousBehavior, \ 68 | AFELFFunction, \ 69 | AFELFIPAddress, \ 70 | AFELFDomain, \ 71 | AFELFURL, \ 72 | AFELFSuspiciousActionMonitored, \ 73 | AFELFCommandAction, \ 74 | AFELFFileActivity 75 | # Macro Specific 76 | from autofocus import \ 77 | AFRelatedMacro 78 | # Coverage Specific 79 | from autofocus import \ 80 | AFC2DomainSignature, \ 81 | AFURLCatogorization, \ 82 | AFAVSignature, \ 83 | AFDNSDownloadSignature 84 | 85 | import sys, argparse, multiprocessing, os, re, json, logging, signal 86 | 87 | 88 | __author__ = "Jeff White [karttoon] @noottrak" 89 | __email__ = "jwhite@paloaltonetworks.com" 90 | __version__ = "1.3.6" 91 | __date__ = "08JUL2019" 92 | 93 | 94 | ####################### 95 | # Check research mode # 96 | ####################### 97 | 98 | research_mode = False 99 | 100 | try: 101 | import configparser 102 | parser = configparser.ConfigParser() 103 | conf_path = os.environ.get("PANW_CONFIG", "~/.config/panw") 104 | parser.read(os.path.expanduser(conf_path)) 105 | research_mode = parser.getboolean("researcher", "enabled") 106 | except: 107 | pass 108 | 109 | SampleFactoryMethod = AFSample.scan if research_mode else AFSample.search 110 | SessionFactoryMethod = AFSession.scan if research_mode else AFSession.search 111 | 112 | ################ 113 | # AFLenz Class # 114 | ################ 115 | 116 | class AFLenzNameSpace(object): 117 | 118 | # A class which wraps an AFlenz command line statement arguments into members 119 | 120 | def __init__(self, 121 | commonality=100, 122 | filter=10000, 123 | ident="query", 124 | limit=200, 125 | output=["all"], 126 | query='{"operator":"all","children":[{"field":"sample.malware","operator":"is","value":1}]}', 127 | quiet=True, 128 | run_type="hash_scrape", 129 | write=False, 130 | platform="all"): 131 | self.commonality = commonality 132 | self.filter = filter 133 | self.ident = ident 134 | self.limit = limit 135 | self.output = output.split(",") 136 | self.query = query 137 | self.quiet = quiet 138 | self.run = run_type 139 | self.special = [] 140 | self.write = write 141 | self.platform = platform 142 | 143 | 144 | #################### 145 | # Build structures # 146 | #################### 147 | def _build_field_structures(values_as_list=False): 148 | 149 | # We should use the private mappings for analysis sections used by the af lib - this will ensure we're always up to 150 | # date 151 | 152 | # Fields we don't want to return 153 | ignore_fields = ('apk_certificate_id',) 154 | 155 | # Fields that aren't part of the mapping that need to be included 156 | other_fields = ["default", "digital_signer", "imphash"] 157 | 158 | # Load up the keys to build, and then build either a list or a dictionary based on function input 159 | keys_to_prep = list(_analysis_2_class_map.keys()) + list(_coverage_2_class_map.keys()) + other_fields 160 | return {k: [] if values_as_list else {} for k in keys_to_prep if k not in ignore_fields} 161 | 162 | def build_field_dict(): 163 | return _build_field_structures() 164 | 165 | def build_field_list(): 166 | return _build_field_structures(values_as_list=True) 167 | 168 | ############################### 169 | # MESSAGE PROCESSING FUNCTION # 170 | ############################### 171 | 172 | def message_proc(message, args): 173 | 174 | if args.write: 175 | file_handle = open(args.write, "a") 176 | file_handle.write(("%s\n" % message)) 177 | else: 178 | print(message) 179 | 180 | return 181 | 182 | 183 | ########################## 184 | # AF QUERY SECTION BELOW # 185 | ########################## 186 | 187 | # Af Query Function 188 | # Takes a type of query and the query itself as input. Example: af_query("hash",) 189 | # Returns a properly formatted autofocus query to be passed to the autofocus API 190 | 191 | def af_query(ident, query): 192 | 193 | if ident == "query": 194 | return query 195 | 196 | # A callable to find the proper field_value for the input_type hash, based on the query_value 197 | def map_hash_value(qv): 198 | if len(qv) == 32: 199 | return "sample.md5" 200 | if len(qv) == 40: 201 | return "sample.sha1" 202 | if len(qv) == 64: 203 | return "sample.sha256" 204 | raise Exception("Unknown hash type") 205 | 206 | # Create a map of input_type to field_value 207 | field_map = { 208 | "connection" : "sample.tasks.connection", 209 | "dns" : "alias.domain", 210 | "email" : "alias.email", 211 | "file" : "sample.tasks.file", 212 | "filename" : "alias.filename", 213 | "fileurl" : "session.fileurl", 214 | "hash" : map_hash_value, 215 | "hash_list" : "sample.sha256", 216 | "http" : "sample.tasks.http", 217 | "ip" : "alias.ip_address", 218 | "mutex" : "sample.tasks.mutex", 219 | "process" : "sample.tasks.process", 220 | "registry" : "sample.tasks.registry", 221 | "service" : "sample.tasks.service", 222 | "tag" : "sample.tag", 223 | "threat" : "sample.threat_name", 224 | "url" : "alias.url", 225 | "user_agent" : "sample.tasks.user_agent" 226 | } 227 | 228 | # Create a map of input_type to operator 229 | operator_map = { 230 | "hash" : "is", 231 | "user_agent" : "is", 232 | "tag" : "is in the list", 233 | "hash_list" : "is in the list" 234 | } 235 | 236 | # Lookup the operator to use with this input type 237 | operator_value = operator_map.get(ident, "contains") 238 | 239 | try: 240 | # Get the field value from the map 241 | field_value = field_map[ident] 242 | 243 | # Is the query value callable? Call it with the query_value to get the field value (hashes) 244 | if isfunction(field_value): 245 | field_value = field_value(query) 246 | except Exception as e: 247 | # Mimic the original catch all, if we don't know what the field is, just exit 248 | raise e 249 | 250 | # Everything that is a list (including hash_list and tag) 251 | if operator_value == "is in the list": 252 | params = [v.strip() for v in query.split(",")] 253 | 254 | # if we have less than 999 params, we only need one query field 255 | if len(params) <= 999: 256 | return '{"operator":"all","children":[{"field":"%s","operator":"%s","value":[%s]}]}' % (field_value, operator_value, ",".join(['"{}"'.format(v) for v in params])) 257 | 258 | else: 259 | # split our params into a list of lists so as to create queries with <=999 elements each. 260 | chunked_params = [params[index:index + 999] for index in range(0, len(params), 999)] 261 | 262 | # Build multiple groups of "in the list" queries 263 | groups = ",".join(['{"field":"%s","operator":"%s","value":[%s]}' % (field_value, operator_value, ",".join(['"{}"'.format(v) for v in chunk])) for chunk in chunked_params]) 264 | 265 | # compile them into the final query. 266 | return '{"operator":"any","children":[%s]}' % groups 267 | else: 268 | 269 | return '{"operator":"all","children":[{"field":"%s","operator":"%s","value":"%s"}]}' % (field_value, operator_value, query) 270 | 271 | 272 | ########################### 273 | # FUNCTION SECTIONS BELOW # 274 | ########################### 275 | 276 | # Hash Library Function 277 | # Builds the hash library which is used by every other function 278 | # Returns data as dictionary with each key being the hash and a dictionary value with each section featuring a list {hash:{section:[value1,value2]}} 279 | 280 | def hash_library(args): 281 | 282 | if not args.quiet: 283 | message_proc("\n[+] hashes [+]\n", args) 284 | 285 | query = af_query(args.ident, args.query) 286 | 287 | async def _do_async_work(): 288 | 289 | results = {} 290 | 291 | # A coroutine for pulling sample data 292 | async def _hash_lookup(queue, async_request): 293 | 294 | while True: 295 | 296 | sha256 = await queue.get() 297 | 298 | if not args.quiet: 299 | message_proc(sha256, args) 300 | 301 | results[sha256] = await hash_lookup(args, sha256, async_request) 302 | 303 | queue.task_done() 304 | 305 | async with aiohttp.ClientSession() as session: 306 | 307 | queue = asyncio.Queue() 308 | 309 | # Create a factory worker that's using our async_request and session 310 | async_request = AsyncRequest(session=session) 311 | sample_factory = AFSampleFactory(async_request=async_request) 312 | 313 | logging.info("Running query: %s", query) 314 | 315 | # Load the sample sha256s into the queue 316 | async for sample in sample_factory.search(query, limit=args.limit): 317 | queue.put_nowait(sample.sha256) 318 | 319 | logging.info("Finished running query: %s", query) 320 | 321 | # Let's spin up 10 task workers 322 | tasks = [asyncio.create_task(_hash_lookup(queue, async_request)) for _ in range(0, 10)] 323 | 324 | # Run the tasks against what's in the queue 325 | await queue.join() 326 | 327 | # All the work in the queue is done, signal that the running tasks 328 | # should shutodnw (no more message going into the queue) 329 | for task in tasks: 330 | task.cancel() 331 | 332 | await asyncio.gather(*tasks, return_exceptions=True) # Wait for all the tasks to complete 333 | 334 | return results 335 | 336 | loop = asyncio.get_event_loop() 337 | result_data = loop.run_until_complete(_do_async_work()) 338 | loop.stop() 339 | return result_data 340 | 341 | 342 | # Hash Lookup Function 343 | # Basic hash lookup for a sample 344 | # Provides raw data for each section requested 345 | async def hash_lookup(args, query, async_request=None): 346 | 347 | if not async_request: 348 | async_request = AsyncRequest() 349 | 350 | sample_factory = AFSampleFactory(async_request=async_request) 351 | analysis_factory = AFAnalysisFactory(async_request=async_request) 352 | coverage_factory = AFCoverageFactory(async_request=async_request) 353 | 354 | # Dictionary mapping the raw data for each type of sample analysis 355 | analysis_data = build_field_list() 356 | 357 | # Create copies of these maps so we can alter them without hurting the lib 358 | analysis_data_map = _class_2_analysis_map.copy() 359 | coverage_data_map = _class_2_coverage_map.copy() 360 | 361 | # This may speed up large queries by reducing the volume of data returned from the API 362 | if args.output == ["all"]: 363 | section_value = None 364 | else: 365 | section_value = [] 366 | 367 | for wanted_section in args.output: 368 | for af_cls, section in analysis_data_map.items(): 369 | if wanted_section == section: 370 | section_value.append(af_cls) 371 | for af_cls, section in coverage_data_map.items(): 372 | if wanted_section == section: 373 | section_value.append(af_cls) 374 | 375 | # Specify platform to restrict results further 376 | if args.platform == "all": 377 | platform_value = None 378 | else: 379 | platform_value = [] 380 | 381 | for platform in args.platform.split(","): 382 | 383 | platform_value.append(platform) 384 | 385 | # If there are no counts for the activity, ignore them for the filter 386 | async for sample in sample_factory.search(af_query("hash", query)): 387 | 388 | # Coverage Specific Details 389 | if args.run == "coverage_scrape": 390 | 391 | # We don't want all of the attributes from the coverage objects, only interesting ones. Map them out here 392 | interesting_attrs_map = { 393 | AFURLCatogorization: ("url", "category"), 394 | AFDNSDownloadSignature: 395 | ("domain", "name", "time", "first_daily_release", "latest_daily_release", 396 | "current_daily_release"), 397 | AFC2DomainSignature: 398 | ("domain", "name", "time", "first_daily_release", "latest_daily_release", 399 | "current_daily_release"), 400 | AFAVSignature: 401 | ("name", "time", "first_daily_release", "latest_daily_release", "current_daily_release"), 402 | } 403 | 404 | for coverage in await coverage_factory.get_coverage_by_hash(sample.sha256): 405 | # Pull the section, get the attrs by class type, then add them as CSV to the analysis_data. 406 | # Will be empty string if we add new coverage that doesn't map interesting attrs 407 | section = analysis_data_map.get(type(coverage), "default") 408 | interesting_attrs = interesting_attrs_map.get(type(coverage), []) 409 | analysis_data[section] = " , ".join([str(getattr(coverage, v)) for v in interesting_attrs]) 410 | 411 | else: 412 | 413 | # Sample Analysis Specific Details 414 | for analysis in await analysis_factory.get_analyses_by_hash(sample.sha256, 415 | sections=section_value, platforms=platform_value): 416 | 417 | analysis_data_section = analysis_data_map.get(type(analysis), "default") 418 | 419 | try: 420 | 421 | if args.special == "bgm": 422 | raw_line = get_bgm(analysis) 423 | else: 424 | raw_line = analysis._raw_line 425 | 426 | # Filter based on established values for low and high-distribution of malware artifacts, otherwise filter on aggregate counts for uniquness 427 | if args.filter == "suspicious": 428 | if (analysis.malware_count > (analysis.benign_count * 3)) and (analysis.malware_count >= 500): 429 | analysis_data[analysis_data_section].append(raw_line) 430 | 431 | elif args.filter == "highly_suspicious": 432 | if analysis.malware_count > (analysis.benign_count * 3) and (analysis.malware_count < 500): 433 | analysis_data[analysis_data_section].append(raw_line) 434 | 435 | elif args.filter == "all_suspicious": 436 | if analysis.malware_count > (analysis.benign_count * 3) and (analysis.malware_count >= 1): 437 | analysis_data[analysis_data_section].append(raw_line) 438 | 439 | elif (analysis.benign_count + analysis.grayware_count + analysis.malware_count) < int(args.filter): 440 | analysis_data[analysis_data_section].append(raw_line) 441 | except: 442 | pass 443 | 444 | # Handle Behaviors which have no BGM values 445 | if type(analysis) == AFBehaviorTypeAnalysis or type(analysis) == AFBehaviorAnalysis: 446 | analysis_data[analysis_data_section].append(analysis._raw_line) 447 | 448 | if sample.imphash: 449 | analysis_data["imphash"].append(sample.imphash) 450 | 451 | if sample.digital_signer: 452 | analysis_data["digital_signer"].append(sample.digital_signer) 453 | 454 | return analysis_data 455 | 456 | 457 | # BGM Function 458 | # Returns human readable B(enign), G(rayware), and M(alware) counts 459 | # Reduces large numbers to abbreivated versions 460 | 461 | def get_bgm(analysis): 462 | 463 | count_list = [str(analysis.benign_count), str(analysis.grayware_count), str(analysis.malware_count)] 464 | raw_line = "" 465 | 466 | for number in count_list: 467 | 468 | if len(number) == 5: # 12345 = 12.3K 469 | number = "%s.%sK" % (number[:2], number[2]) 470 | if len(number) == 6: # 123456 = 123K 471 | number = "%sK" % (number[:3]) 472 | if len(number) == 7: # 1234567 = 1.2M 473 | number = "%s.%sM" % (number[:1], number[1]) 474 | if len(number) == 8: # 12345678 = 12.3M 475 | number = "%s.%sK" % (number[:2], number[2]) 476 | if len(number) == 9: # 123456789 = 123M 477 | number = "%sM" % (number[:3]) 478 | if len(number) >= 10: # 1234567890 = 1B 479 | number = "%sB" % (number[:3]) 480 | 481 | raw_line += "%-5s " % number 482 | 483 | raw_line += "| %s" % analysis._raw_line 484 | 485 | return raw_line 486 | 487 | 488 | # Common Artifacts Function 489 | # Identifies lines that exist, per section, in every identified sample 490 | # Must be a 100% match, unless adjusted by -c flag, across all samples to be reported, thus samples that unique every install may not have certain entries appear 491 | 492 | def common_artifacts(args): 493 | 494 | commonality = float(args.commonality)/float(100) 495 | 496 | # Used for collecting all of the artifacts and counts 497 | compare_data = build_field_dict() 498 | 499 | # Final collection of all common artifacts 500 | common_data = build_field_list() 501 | 502 | count = 0 503 | hashes = hash_library(args) 504 | 505 | for hash in list(hashes.keys()): 506 | 507 | # Sample data 508 | hash_data = build_field_dict() 509 | 510 | for section in hashes[hash]: 511 | for value in hashes[hash][section]: 512 | 513 | if value in compare_data[section] and value not in hash_data[section]: 514 | compare_data[section][value] += 1 515 | hash_data[section][value] = 1 516 | 517 | if value not in compare_data[section] and value not in hash_data[section]: 518 | hash_data[section][value] = 1 519 | compare_data[section][value] = 1 520 | count += 1 521 | 522 | for section in compare_data: 523 | 524 | for value in compare_data[section]: 525 | if float(compare_data[section][value])/float(count) >= commonality: 526 | 527 | match_percent = int(float(compare_data[section][value])/float(count) * 100) 528 | 529 | if "range" in args.special: 530 | common_data[section].append("%-3s | " % (match_percent) + value) 531 | else: 532 | common_data[section].append(value) 533 | 534 | common_data["count"] = count # Keep track of how many samples processed 535 | common_data["hashes"] = list(hashes.keys()) 536 | 537 | return common_data 538 | 539 | 540 | # Common Pieces Function 541 | # Similar to the "comnmon_artifact" function, but further breaks down each line to look for commonalities 542 | # Will have more hits but likely less accurate 543 | 544 | def common_pieces(args): 545 | 546 | commonality = float(args.commonality)/float(100) 547 | 548 | # Used for collecting all of the artifacts and counts 549 | compare_data = build_field_dict() 550 | 551 | # Final collection of all common pieces 552 | common_pieces = build_field_list() 553 | 554 | count = 0 555 | hashes = hash_library(args) 556 | 557 | for hash in list(hashes.keys()): 558 | 559 | # Sample data 560 | hash_data = build_field_dict() 561 | for section in hashes[hash]: 562 | for value in hashes[hash][section]: 563 | 564 | section_data = value.split(" , ") 565 | 566 | for piece in section_data: 567 | 568 | if piece in compare_data[section] and piece not in hash_data[section]: 569 | compare_data[section][piece] += 1 570 | hash_data[section][piece] = 1 571 | 572 | if piece not in compare_data[section] and piece not in hash_data[section]: 573 | hash_data[section][piece] = 1 574 | compare_data[section][piece] = 1 575 | count += 1 576 | 577 | for section in compare_data: 578 | for value in compare_data[section]: 579 | if float(compare_data[section][value])/float(count) >= commonality: 580 | 581 | match_percent = int(float(compare_data[section][value])/float(count) * 100) 582 | 583 | if "range" in args.special: 584 | common_pieces[section].append("%-3s | " % (match_percent) + value) 585 | else: 586 | common_pieces[section].append(value) 587 | 588 | common_pieces["count"] = count # Keep track of how many samples processed 589 | common_pieces["hashes"] = list(hashes.keys()) 590 | 591 | # Clear out behavior descriptions so it doesn't print - doesn't really make sense for this context 592 | # Comment out to add them back in 593 | common_pieces["behavior"] = [] 594 | 595 | return common_pieces 596 | 597 | 598 | # Unique Sessions Function 599 | # Will gather session data from the identified samples and then report back the unique values per section 600 | # Session data isn't normalized quite the same as sample data so this may be more error-prone 601 | def uniq_sessions(args): 602 | 603 | # We want to track session value counts - default to a dict that has a dict that defaults to 0 604 | session_data = defaultdict(lambda: defaultdict(lambda: 0)) 605 | 606 | query = af_query(args.ident, args.query) 607 | 608 | session_count = 0 609 | 610 | for session in SessionFactoryMethod(query, limit=args.limit): 611 | 612 | session_count += 1 613 | 614 | for section in session.__dict__.keys(): 615 | 616 | # We only care about non-private attributes or the private attr _vsys 617 | if section.startswith("_") and section != "_vsys": 618 | continue 619 | 620 | attr_value = getattr(session, section) 621 | if attr_value is not None: # Only tracking attr_values that aren't None 622 | session_data[section][attr_value] += 1 623 | 624 | if args.special == "count": 625 | res = {} 626 | for attr, values in session_data.items(): 627 | res[attr] = ["%-4s | %s" % (v, k) for k, v in values.items()] 628 | res['count'] = session_count 629 | return res 630 | 631 | res = {k: list(v.keys()) for k, v in session_data.items()} 632 | res["count"] = session_count 633 | return res 634 | 635 | 636 | # Count Values Function 637 | # Totals up the unique values per section 638 | # Works with hash and session function 639 | 640 | def count_values(count_list, args): 641 | 642 | for section in count_list: 643 | 644 | if args.output == ["all"] or section in args.output: 645 | 646 | unique_values = [] 647 | 648 | for value in count_list[section]: 649 | unique_values.append("%-4s | %s" % (count_list[section].count(value), value)) 650 | 651 | count_list[section] = [] 652 | 653 | for value in unique_values: 654 | if value not in count_list[section]: 655 | count_list[section].append(value) 656 | 657 | return count_list 658 | 659 | 660 | # Hash Scraper Function 661 | # Extracts all data from each section of the identified samples 662 | # BGM filtering is done on the entire line 663 | 664 | def hash_scrape(args): 665 | 666 | hash_data = build_field_list() 667 | 668 | count = 0 669 | hashes = hash_library(args) 670 | 671 | for hash in hashes: 672 | for section in hashes[hash]: 673 | unique_list = [] 674 | for value in hashes[hash][section]: 675 | 676 | if args.special == "count" and value not in unique_list: 677 | hash_data[section].append(value) 678 | unique_list.append(value) 679 | else: 680 | if value not in hash_data[section]: 681 | hash_data[section].append(value) 682 | count += 1 683 | 684 | if args.special == "count": 685 | hash_data = count_values(hash_data, args) 686 | 687 | hash_data["count"] = count # Keep track of how many samples processed 688 | hash_data["hashes"] = list(hashes.keys()) 689 | 690 | return hash_data 691 | 692 | 693 | # HTTP Scraper Function 694 | # Extracts all HTTP requests made from the identified samples 695 | # BGM filtering is done on the entire line 696 | 697 | def http_scrape(args): 698 | 699 | http_data = {"http": []} 700 | count = 0 701 | hashes = hash_library(args) 702 | 703 | for hash in list(hashes.keys()): 704 | 705 | sample_data = hashes[hash] 706 | 707 | for entry in sample_data["http"]: 708 | 709 | http_list = entry.split(" , ") 710 | url_value = "hxxp://" + http_list[0] + http_list[2] 711 | 712 | if url_value not in http_data["http"]: 713 | http_data["http"].append(url_value) 714 | 715 | count += 1 716 | 717 | http_data["count"] = count # Keep track of how many samples processed 718 | http_data["hashes"] = list(hashes.keys()) 719 | 720 | return http_data 721 | 722 | 723 | # DNS Scraper Function 724 | # Extracts all DNS queries made from the identified samples 725 | # BGM filtering is done on the entire line 726 | 727 | def dns_scrape(args): 728 | 729 | dns_data = {"dns": []} 730 | count = 0 731 | hashes = hash_library(args) 732 | 733 | for hash in list(hashes.keys()): 734 | 735 | sample_data = hashes[hash] 736 | 737 | for entry in sample_data["dns"]: 738 | 739 | dns_list = entry.split(" , ") 740 | dns_query = dns_list[0] 741 | 742 | if dns_query not in dns_data["dns"]: 743 | dns_data["dns"].append(dns_query) 744 | 745 | count += 1 746 | 747 | dns_data["count"] = count # Keep track of how many samples processed 748 | dns_data["hashes"] = list(hashes.keys()) 749 | 750 | return dns_data 751 | 752 | 753 | # Mutex Scraper Function 754 | # Extracts all mutexes created within the identified samples 755 | # BGM filtering is done on the entire line 756 | 757 | def mutex_scrape(args): 758 | 759 | mutex_data = {"mutex": []} 760 | count = 0 761 | hashes = hash_library(args) 762 | 763 | for hash in list(hashes.keys()): 764 | 765 | sample_data = hashes[hash] 766 | 767 | for entry in sample_data["mutex"]: 768 | 769 | mutex_list = entry.split(" , ") 770 | mutex_value = mutex_list[2] 771 | 772 | if mutex_value not in mutex_data["mutex"]: 773 | mutex_data["mutex"].append(mutex_value) 774 | 775 | count += 1 776 | 777 | mutex_data["count"] = count # Keep track of how many samples processed 778 | mutex_data["hashes"] = list(hashes.keys()) 779 | 780 | return mutex_data 781 | 782 | 783 | # Dropped File scraper function 784 | # Extracts all dropped files from the identified samples 785 | # BGM filtering is done on the entire line 786 | 787 | def dropped_file_scrape(args): 788 | 789 | dropped_file_data = {"dropped_files": []} 790 | count = 0 791 | hashes = hash_library(args) 792 | 793 | for hash in hashes: 794 | 795 | # At the time of writing, for each case we have to process two sections to find possible dropped files 796 | # * Dropped files in the process section contain "hash" as the processes' action 797 | # * Dropped files in the file section contain "sha256" in the file section 798 | # We'll handle these two cases in order 799 | 800 | # List of directories for filtering 801 | dir_filters = ["Documents and Settings\\Administrator\\Local Settings\\Temporary Internet Files\\Content.Word\\", 802 | "Documents and Settings\\Administrator\\Application Data\\Microsoft\\Office\\Recent\\", 803 | "\\Microsoft\\Windows\\Temporary Internet Files\\Content.Word\\", 804 | "\\Microsoft\\Office\\Recent\\", 805 | "documents and settings\\administrator\\~$", 806 | "Users\\Administrator\\~$", 807 | "\\AppData\\Local\\Microsoft\\Office\\14.0\\OfficeFileCache\\", 808 | "Documents and Settings\\Administrator\\Local Settings\\Temporary Internet Files\\Content.MSO\\", 809 | "\\AppData\\Roaming\\Microsoft\\Templates\\", 810 | "\\AppData\\Roaming\\Microsoft\\Word\\STARTUP\\" 811 | ] 812 | 813 | # Process "process" entries 814 | sample_data = hashes[hash] 815 | 816 | for entry in sample_data["process"]: 817 | 818 | if "hash" in entry.split(",")[1]: 819 | 820 | file_sha256 = entry.split(",")[-1].strip() 821 | file_path = entry.split(",")[2].strip() 822 | 823 | # Don't return the hash of the file we're looking at already. 824 | if hash == file_sha256.lower(): 825 | continue 826 | 827 | res_string = "%s | %s" % (file_sha256, file_path) 828 | 829 | if res_string not in dropped_file_data["dropped_files"]: 830 | dropped_file_data["dropped_files"].append(res_string) 831 | 832 | # Process "file" entries 833 | for entry in sample_data["file"]: 834 | 835 | if "sha256=" in entry: 836 | 837 | file_sha256 = re.search("[A-Za-z0-9]{64}", entry.split(",")[-1]).group() 838 | file_path = entry.split(",")[2].strip() 839 | 840 | # Ignore if the result is the same as the hash of the file 841 | if hash == file_sha256.lower(): 842 | continue 843 | 844 | res_string = "%s | %s" % (file_sha256.upper(), file_path) 845 | 846 | # Filter files from specific directories 847 | break_flag = 0 848 | 849 | for filter in dir_filters: 850 | 851 | if filter.lower() in file_path.lower(): 852 | logging.info("Filtering dropped file %s | %s as it matches filter %s" % (file_path, file_sha256, filter)) 853 | break_flag = 1 854 | break 855 | 856 | if break_flag == 0: 857 | 858 | if res_string not in dropped_file_data["dropped_files"]: 859 | logging.info("Adding result %s as it didn't match any filters", res_string) 860 | dropped_file_data["dropped_files"].append(res_string) 861 | 862 | count += 1 863 | 864 | for entry in dropped_file_data["dropped_files"]: 865 | if list(map(str.lower, dropped_file_data["dropped_files"])).count(entry.lower()) > 1: 866 | dropped_file_data["dropped_files"].remove(entry) 867 | 868 | dropped_file_data["dropped_files"].sort() 869 | 870 | dropped_file_data["count"] = count 871 | dropped_file_data["hashes"] = list(hashes.keys()) 872 | 873 | return dropped_file_data 874 | 875 | 876 | # Service Scraper Function 877 | # Extracts all service names from the identified samples 878 | # BGM filtering is done on the entire line 879 | 880 | def service_scrape(args): 881 | 882 | service_data = {"service": []} 883 | count = 0 884 | hashes = hash_library(args) 885 | 886 | for hash in list(hashes.keys()): 887 | 888 | sample_data = hashes[hash] 889 | 890 | for entry in sample_data["service"]: 891 | 892 | service_list = entry.split(" , ") 893 | service_query = service_list[2] 894 | 895 | if service_query not in service_data["service"]: 896 | service_data["service"].append(service_query) 897 | count += 1 898 | 899 | service_data["count"] = count # Keep track of how many samples processed 900 | service_data["hashes"] = list(hashes.keys()) 901 | 902 | return service_data 903 | 904 | 905 | # Flat file reader function 906 | # Reads lines in from a file while checking for sha256 hashes. 907 | # Returns a list of hashes. 908 | 909 | def fetch_from_file(args,input_file): 910 | 911 | if args.ident == "file_hashes": 912 | hashlist = [] 913 | 914 | if not args.quiet: 915 | message_proc("\n[+] Attempting to read hashes from %s" % input_file, args) 916 | 917 | try: 918 | with open(input_file, "r") as fh: 919 | 920 | for line in fh.readlines(): 921 | 922 | line = line.strip() 923 | 924 | if re.match("^[0-9a-zA-Z]{64}$",line): 925 | hashlist.append(line) 926 | else: 927 | # Ignore any malformed hashes or bad lines 928 | pass 929 | 930 | except IOError as e: 931 | message_proc("\n[!] Error: %s - %s" % (e.strerror, e.filename), args) 932 | sys.exit(2) 933 | 934 | return hashlist 935 | 936 | elif args.ident == "file_query": 937 | 938 | if not args.quiet: 939 | message_proc("\n[+] Attempting to read query from %s" % input_file, args) 940 | 941 | try: 942 | with open(input_file, "r") as fh: 943 | 944 | query = (fh.read()).strip() 945 | 946 | except IOError as e: 947 | message_proc("\n[!] Error: %s - %s" % (e.strerror, e.filename), args) 948 | sys.exit(2) 949 | 950 | return query 951 | 952 | 953 | # Diff Function 954 | # Extracts all data from each section of the identified samples and finds differences 955 | # BGM filtering is done on the entire line 956 | 957 | def diff(args): 958 | 959 | hash_data = build_field_list() 960 | 961 | # Only compare two hashes for diff 962 | args.limit = 2 963 | 964 | hashes = hash_library(args) 965 | hash_list = [] 966 | 967 | for hash in hashes: 968 | hash_list.append(hash) 969 | 970 | message_proc("\n[+] diff [+]\n\n< | %s\n> | %s" % (hash_list[0], hash_list[1]), args) 971 | 972 | count = 0 973 | 974 | for hash in hash_list: 975 | for section in hashes[hash]: 976 | 977 | if count == 1 and hash_data[section] != []: 978 | hash_data[section].append("---") 979 | 980 | for value in hashes[hash][section]: 981 | if count == 0: 982 | if value not in hashes[hash_list[1]][section]: 983 | hash_data[section].append("< | " + value) 984 | #else: 985 | #hash_data[section].append(value) # Prints matching line, uncomment to add in 986 | else: 987 | if value not in hashes[hash_list[0]][section]: 988 | hash_data[section].append("> | " + value) 989 | count += 1 990 | 991 | hash_data["count"] = count 992 | hash_data["hashes"] = list(hashes.keys()) 993 | 994 | return hash_data 995 | 996 | 997 | # Tag Info 998 | # Pulls back specific tag details 999 | # Output should be similar to what's presented when looking at a tag in AF 1000 | 1001 | def tag_info(args): 1002 | 1003 | # Get tag info 1004 | tag_info = AFTag.get(args.query) 1005 | tag_details = "" 1006 | 1007 | tag_details += "\n%-15s : %s\n" % ("Tag Name", tag_info.name) 1008 | tag_details += "%-15s : %s\n" % ("Tag Public Name", tag_info.public_name) 1009 | tag_details += "%-15s : %s\n" % ("Tag Count", tag_info.count) 1010 | tag_details += "%-15s : %s\n" % ("Tag Created", tag_info.created) 1011 | tag_details += "%-15s : %s\n" % ("Tag Last Hit", tag_info.last_hit) 1012 | tag_details += "%-15s : %s\n" % ("Tag Class", tag_info.tag_class) 1013 | tag_details += "%-15s : %s\n" % ("Tag Status", tag_info.status) 1014 | tag_details += "%-15s : %s\n" % ("Tag Source", tag_info.scope) 1015 | tag_details += "%-15s : %s\n" % ("Tag Description", tag_info.description) 1016 | tag_details += "%-15s : \n" % ("Tag References") 1017 | for entry in tag_info.references: 1018 | try: 1019 | tag_details += "\t[Source] %s | [Title] %s | [URL] %s\n" % (entry.source, entry.title, entry) 1020 | except: 1021 | pass 1022 | 1023 | tag_details += "%-15s : " % ("Tag Queries") 1024 | 1025 | for tagQuery in tag_info.tag_definitions: 1026 | tag_details += ("\n\n" + str(tagQuery)) 1027 | 1028 | message_proc("\n[+] Tag Info [+]\n%s\n" % tag_details, args) 1029 | 1030 | sys.exit(1) 1031 | 1032 | 1033 | # Tag Checker 1034 | # Tries to identify what artifact in a sample caused it to be tagged with the supplied tag 1035 | 1036 | def tag_check(args): 1037 | 1038 | # Remove filter so all artifacts are checked 1039 | args.filter = 1000000000 1040 | 1041 | tag_data = {"tag_value" : args.query.split(",")[0], 1042 | "hash_value" : args.query.split(",")[1] 1043 | } 1044 | 1045 | # Get tag definitions 1046 | logging.info("Retrieving tag definitions for tag %s" % tag_data["tag_value"]) 1047 | tag_info = AFTag.get(tag_data["tag_value"]) 1048 | tag_def = tag_info.tag_definitions 1049 | 1050 | # Get hash info 1051 | args.query = tag_data["hash_value"] 1052 | args.ident = "hash" 1053 | 1054 | logging.info("Retrieving data for sample %s" % tag_data["hash_value"]) 1055 | 1056 | loop = asyncio.get_event_loop() 1057 | hash_detail = loop.run_until_complete(hash_lookup(args, tag_data["hash_value"])) 1058 | loop.stop() 1059 | 1060 | hash_data = build_field_list() 1061 | 1062 | sections = build_field_list() 1063 | matches = {} 1064 | 1065 | for section in hash_detail: 1066 | for value in hash_detail[section]: 1067 | if value not in hash_data[section]: 1068 | hash_data[section].append(value) 1069 | 1070 | # Perform the tag check across sections 1071 | def match_check(entry, match_flag): 1072 | 1073 | for section in sections: 1074 | 1075 | if section in entry["field"]: 1076 | 1077 | if section != "behavior_type" and section != "behavior": 1078 | 1079 | # Contains / Is (search within) 1080 | if entry["operator"] == "contains" or entry["operator"] == "is": 1081 | 1082 | for line in hash_detail[section]: 1083 | 1084 | if entry["value"].lower() in line.lower(): # AF is case-insensitive here 1085 | 1086 | match_flag = 1 1087 | 1088 | entry_check = str(entry) 1089 | if entry_check not in matches: 1090 | matches[entry_check] = {} 1091 | if section not in matches[entry_check]: 1092 | matches[entry_check][section] = [] 1093 | if line not in matches[entry_check][section]: 1094 | matches[entry_check][section].append(line) 1095 | 1096 | # Is in the list (split and re-iterate each) 1097 | if entry["operator"] == "is in the list": 1098 | 1099 | for value in entry["value"]: 1100 | 1101 | for line in hash_detail[section]: 1102 | 1103 | if value.lower() in line.lower(): 1104 | 1105 | match_flag = 1 1106 | 1107 | entry_check = str(entry) 1108 | if entry_check not in matches: 1109 | matches[entry_check] = {} 1110 | if section not in matches[entry_check]: 1111 | matches[entry_check][section] = [] 1112 | if line not in matches[entry_check][section]: 1113 | matches[entry_check][section].append(line) 1114 | 1115 | # AF Regex (modify AF regex to compliant statement) 1116 | # Attempt to convert proximity to regex - may be hit or miss 1117 | if entry["operator"] == "regexp" or entry["operator"] == "proximity": 1118 | 1119 | af_regex = ".+".join(entry["value"].split(" ")) 1120 | 1121 | for line in hash_detail[section]: 1122 | 1123 | if re.search(af_regex, line, re.IGNORECASE): 1124 | 1125 | match_flag = 1 1126 | 1127 | entry_check = str(entry) 1128 | if entry_check not in matches: 1129 | matches[entry_check] = {} 1130 | if section not in matches[entry_check]: 1131 | matches[entry_check][section] = [] 1132 | if line not in matches[entry_check][section]: 1133 | matches[entry_check][section].append(line) 1134 | 1135 | return match_flag 1136 | 1137 | logging.info("Found %s queries to check against the sample" % len(tag_def)) 1138 | for query in tag_def: 1139 | 1140 | match_flag = 0 1141 | 1142 | query = json.loads(query.ui_search_definition)#.encode("utf-8")) 1143 | 1144 | # Attempt to wrap each query with a parent query using supplied hash 1145 | # Should narrow down queries that need to be deconstructed for checking 1146 | query_check = str(query) 1147 | 1148 | logging.info("[ ORIGINAL ]\n%s\n" % query_check) 1149 | 1150 | # Poor anchors for reverse converting at the end for special cases 1151 | query_check = query_check.replace('"', "ABCSTARTQU0TEDEF") 1152 | query_check = query_check.replace("'", "ABCSTARTQU0TEDEF") 1153 | query_check = query_check.replace('",', "ABCENDQU0TEDEF") 1154 | query_check = query_check.replace('"}', "ABCDICTQU0TEDEF") 1155 | query_check = query_check.replace('"]', "ABCLISTQU0TEDEF") 1156 | query_check = query_check.replace("\\'\\'", "ABC2XSINGLEDEF") 1157 | query_check = query_check.replace("\\", "ABCBACKSLASHDEF") 1158 | 1159 | # If double quotes in value, temporarily replace them and escape/add back in at the end after all string manipulation 1160 | query_check = query_check.replace('"', 'ABCDOULBEQU0TEDEF') 1161 | 1162 | # General replacements to massage query into acceptable query for API - single to double quotes 1163 | query_check = re.sub("(')", "\"", query_check) 1164 | query_check = re.sub('(")', "\"", query_check) 1165 | query_check = re.sub("(': )", "\": ", query_check) 1166 | query_check = re.sub("(', )", "\", ", query_check) 1167 | query_check = re.sub("('})", "\"}", query_check) 1168 | query_check = re.sub("('])", "\"]", query_check) 1169 | 1170 | # Surround query with hash 1171 | query_check = query_check.replace("[{u'operator': u'any', u'children':", "") 1172 | query_check = '{"operator":"all","children":[{"field":"sample.sha256","operator":"is","value":"%s"},' % tag_data["hash_value"] + query_check + "]}" 1173 | 1174 | # Convert fields back for API 1175 | query_check = query_check.replace("ABCSTARTQU0TEDEF", '"') 1176 | query_check = query_check.replace("ABCENDQU0TEDEF" , '",') 1177 | query_check = query_check.replace("ABCDICTQU0TEDEF" , '"}') 1178 | query_check = query_check.replace("ABCLISTQU0TEDEF" , '"]') 1179 | query_check = query_check.replace("ABCDOULBEQU0TEDEF", '\\"') 1180 | query_check = query_check.replace("ABC2XSINGLEDEF", "''") 1181 | query_check = query_check.replace("ABCBACKSLASHDEF", "\\") 1182 | 1183 | # This fixes a hidden character from a specific query 1184 | query_check = query_check.replace("\\xad","") 1185 | 1186 | # This is more clean-up to get rid of the unicode that gets added with JSON 1187 | query_check = query_check.replace("': u'", "': '")\ 1188 | .replace(" [{u\"", " [{\"")\ 1189 | .replace("\", u\"", "\", \"")\ 1190 | .replace(" {u\"", " {\"")\ 1191 | .replace(" [u\"", " [\"")\ 1192 | .replace(": u\"", ": \"")\ 1193 | .replace(",{u\"", ",{\"") 1194 | 1195 | logging.info("[ MODIFIED ]\n%s\n" % query_check) 1196 | 1197 | try: 1198 | 1199 | for sample in AFSample.search(query_check): 1200 | 1201 | if sample.sha256 == tag_data["hash_value"]: 1202 | 1203 | for entry in query["children"]: 1204 | 1205 | if "field" not in entry: 1206 | 1207 | for child_entry in entry["children"]: 1208 | 1209 | if "field" not in entry: 1210 | 1211 | for child_child_entry in child_entry: # Lots of kids... 1212 | 1213 | if "field" in child_child_entry: 1214 | 1215 | match_flag = match_check(child_entry, match_flag) 1216 | 1217 | else: 1218 | 1219 | match_flag = match_check(child_entry, match_flag) 1220 | 1221 | else: 1222 | match_flag = match_check(entry, match_flag) 1223 | 1224 | if match_flag == 0: 1225 | 1226 | message_proc("\n[+] Unsupported Matched Query [+]\n\n%s" % query_check, args) 1227 | 1228 | except Exception as e: 1229 | 1230 | message_proc("\n[!] Unable to Parse/Check Query [+]\n\n%s" % query, args) 1231 | logging.debug("Failed to parse the following query: \n%s\n%s" % (query, e)) 1232 | 1233 | for entry in matches: 1234 | 1235 | message_proc("\n[+] Matched Query [+]\n\n%s" % entry, args) 1236 | 1237 | for section in matches[entry]: 1238 | 1239 | message_proc("\n[+] %s [+]\n" % section, args) 1240 | 1241 | for line in matches[entry][section]: 1242 | 1243 | message_proc(line, args) 1244 | 1245 | tag_data["count"] = 1 1246 | 1247 | args.filter = 0 1248 | 1249 | logging.info("Completed tag_check function") 1250 | 1251 | return tag_data 1252 | 1253 | 1254 | # Metadata Scraper Function 1255 | # Extracts all metadata data from the identified samples 1256 | # BGM filtering is done on the entire line 1257 | 1258 | def meta_scrape(args): 1259 | results = [] 1260 | 1261 | if not args.quiet: 1262 | message_proc("\n[+] sample_meta [+]\n", args) 1263 | 1264 | query = af_query(args.ident, args.query) 1265 | 1266 | for sample in SampleFactoryMethod(query, limit=args.limit): 1267 | results.append(build_output_string(args, sample, "meta")) 1268 | 1269 | return results 1270 | 1271 | 1272 | # Session Scraper Function 1273 | # Extracts all session data from the identified samples 1274 | # BGM filtering is done on the entire line 1275 | 1276 | def session_scrape(args): 1277 | 1278 | if not args.quiet: 1279 | message_proc("\n[+] session_meta [+]\n", args) 1280 | 1281 | query = af_query(args.ident, args.query) 1282 | 1283 | results = [] 1284 | for session in SessionFactoryMethod(query, limit=args.limit): 1285 | results.append(build_output_string(args, session, "session")) 1286 | return results 1287 | 1288 | 1289 | ######################## 1290 | # OUTPUT SECTION BELOW # 1291 | ######################## 1292 | 1293 | # Output Analysis Function 1294 | # This is what gets printed out to the screen 1295 | # Takes a normalized input of sections and returns the sections requested by the user 1296 | 1297 | def output_analysis(args, sample_data, funct_type): 1298 | 1299 | output = args.output 1300 | 1301 | section_list = [ 1302 | #Session 1303 | "application", 1304 | "account_name", 1305 | "device_country_code", 1306 | "device_country", 1307 | "device_hostname", 1308 | "industry", 1309 | "business_line", 1310 | "device_model", 1311 | "device_serial", 1312 | "device_version", 1313 | "dst_country_code", 1314 | "dst_country", 1315 | "dst_ip", 1316 | "dst_is_private_ip", 1317 | "dst_port", 1318 | "email_recipient", 1319 | "email_charset", 1320 | "email_sender", 1321 | "email_subject", 1322 | "file_name", 1323 | "file_url", 1324 | "is_uploaded", 1325 | "session_id", 1326 | "sha256", 1327 | "src_country_code", 1328 | "src_country", 1329 | "src_ip", 1330 | "src_is_private_ip", 1331 | "src_port", 1332 | "timestamp", 1333 | "upload_source", 1334 | "user_id", 1335 | "_vsys" 1336 | #Sample 1337 | "apk_app_icon", 1338 | "apk_app_name", 1339 | "apk_cert_file", 1340 | #"apk_certificate_id", # Unused currently 1341 | "apk_defined_activity", 1342 | "apk_defined_intent_filter", 1343 | "apk_defined_receiver", 1344 | "apk_defined_sensor", 1345 | "apk_defined_service", 1346 | "apk_digital_signer", 1347 | "apk_embedded_library", 1348 | "apk_embeded_url", 1349 | "apk_internal_file", 1350 | "apk_isrepackaged", 1351 | "apk_packagename", 1352 | "apk_requested_permission", 1353 | "apk_sensitive_api_call", 1354 | "apk_suspicious_action_monitored", 1355 | "apk_suspicious_api_call", 1356 | "apk_suspicious_file", 1357 | "apk_suspicious_pattern", 1358 | "apk_suspicious_string", 1359 | "apk_version_num", 1360 | "behavior", 1361 | "behavior_type", 1362 | "connection", 1363 | "default", 1364 | "digital_signer", 1365 | "dns", 1366 | "dropped_files", 1367 | "elf_commands", 1368 | "elf_domains", 1369 | "elf_file_paths", 1370 | "elf_functions", 1371 | "elf_ip_address", 1372 | "elf_suspicious_behavior", 1373 | "elf_urls", 1374 | "elf_file_activity", 1375 | "elf_command_action", 1376 | "elf_suspicious_action", 1377 | "file", 1378 | "http", 1379 | "imphash", 1380 | "japi", 1381 | "mac_embedded_file", 1382 | "mac_embedded_url", 1383 | "macro", 1384 | "misc", 1385 | "mutex", 1386 | "process", 1387 | "registry", 1388 | "service", 1389 | "summary", 1390 | "user_agent", 1391 | # Coverage 1392 | "dns_sig", 1393 | "fileurl_sig", 1394 | "url_cat", 1395 | "wf_av_sig" 1396 | ] 1397 | 1398 | if "all" in output: 1399 | for entry in section_list: 1400 | if entry in list(sample_data.keys()) and sample_data[entry] != []: 1401 | if not args.quiet: 1402 | message_proc("\n[+] %s [+]\n" % entry, args) 1403 | for value in sample_data[entry]: 1404 | if value != "": 1405 | message_proc(value, args) 1406 | else: 1407 | for entry in output: 1408 | if entry in list(sample_data.keys()) and sample_data[entry] != []: 1409 | if not args.quiet: 1410 | message_proc("\n[+] %s [+]\n" % entry, args) 1411 | for value in sample_data[entry]: 1412 | if value != "": 1413 | message_proc(value, args) 1414 | 1415 | if funct_type == "sample": 1416 | if not args.quiet: 1417 | message_proc("\n[+] processed %s hashes with a BGM filter of %s [+]\n" % (sample_data["count"], str(args.filter)), args) 1418 | elif funct_type == "session": 1419 | if not args.quiet: 1420 | message_proc("\n[+] processed %s sessions [+]\n" % sample_data["count"], args) 1421 | 1422 | 1423 | # Output List Function 1424 | # This just returns sample based meta-data based on the query provided 1425 | # Intended to be filtered/sorted afterwards by "|" pipe delimited characters 1426 | 1427 | def build_output_string(args, item, type): 1428 | 1429 | output = args.output 1430 | 1431 | # 1432 | # Meta 1433 | # 1434 | if type == "meta": 1435 | 1436 | meta_sections = {"tags" : ",".join(item._tags), 1437 | "sha256" : item.sha256, 1438 | "file_type" : item.file_type, 1439 | "create_date" : str(item.create_date), 1440 | "verdict" : item.verdict, 1441 | "file_size" : str(item.size), 1442 | "digital_signer" : item.digital_signer, 1443 | "sha1" : item.sha1, 1444 | "md5" : item.md5, 1445 | "ssdeep" : item.ssdeep, 1446 | "imphash" : item.imphash 1447 | } 1448 | 1449 | print_list = [] 1450 | 1451 | if args.special == "tag_count": 1452 | output = ["tags"] 1453 | 1454 | if "all" in output: # Not literally 'all' in this particular case - more aligned to default UI display of AutoFocus 1455 | 1456 | all_sections = ["sha256", 1457 | "file_type", 1458 | "create_date", 1459 | "verdict", 1460 | "file_size", 1461 | "tags"] 1462 | 1463 | for entry in all_sections: 1464 | if meta_sections[entry] == None: 1465 | print_list.append("None") 1466 | else: 1467 | print_list.append(meta_sections[entry]) 1468 | 1469 | else: 1470 | for entry in output: 1471 | if entry in meta_sections: 1472 | print_list.append("%s" % meta_sections[entry]) 1473 | 1474 | # 1475 | # Session 1476 | # 1477 | elif type == "session": 1478 | 1479 | meta_sections = { 1480 | "account_name" : item.account_name, 1481 | "application" : item.application, 1482 | "device_country_code" : item.device_country_code, 1483 | "device_country" : item.device_country, 1484 | "device_hostname" : item.device_hostname, 1485 | "industry" : item.industry, 1486 | "business_line" : item.business_line, 1487 | "device_model" : item.device_model, 1488 | "device_serial" : item.device_serial, 1489 | "device_version" : item.device_version, 1490 | "dst_country_code" : item.dst_country_code, 1491 | "dst_country" : item.dst_country, 1492 | "dst_ip" : item.dst_ip, 1493 | "dst_is_private_ip" : item.dst_is_private_ip, 1494 | "dst_port" : item.dst_port, 1495 | "email_recipient" : item.email_recipient, 1496 | "email_charset" : item.email_charset, 1497 | "email_sender" : item.email_sender, 1498 | "email_subject" : item.email_subject, 1499 | "file_name" : item.file_name, 1500 | "file_url" : item.file_url, 1501 | "is_uploaded" : item.is_uploaded, 1502 | "session_id" : item.session_id, 1503 | "sha256" : item.sha256, 1504 | "src_country_code" : item.src_country_code, 1505 | "src_country" : item.src_country, 1506 | "src_ip" : item.src_ip, 1507 | "src_is_private_ip" : item.src_is_private_ip, 1508 | "src_port" : item.src_port, 1509 | "timestamp" : str(item.timestamp), 1510 | "upload_source" : item.upload_source, 1511 | "user_id" : item.user_id, 1512 | "_vsys" : item._vsys} 1513 | 1514 | print_list = [] 1515 | 1516 | if "all" in output: # Not literally 'all' in this particular case - more aligned to default UI display of AutoFocus 1517 | 1518 | all_sections = ["sha256", 1519 | "timestamp", 1520 | "account_name", 1521 | "email_sender", 1522 | "email_subject", 1523 | "file_name", 1524 | "file_url"] 1525 | 1526 | for entry in all_sections: 1527 | if meta_sections[entry] == None: 1528 | print_list.append("None") 1529 | else: 1530 | print_list.append(meta_sections[entry]) 1531 | 1532 | else: 1533 | for entry in output: 1534 | if entry in meta_sections: 1535 | print_list.append("%s" % meta_sections[entry]) 1536 | 1537 | return print_list 1538 | 1539 | 1540 | def output_list(args): 1541 | 1542 | count = 0 1543 | results = [] 1544 | 1545 | # 1546 | # Meta Scrape 1547 | # 1548 | if args.run == "meta_scrape": 1549 | 1550 | results = meta_scrape(args) 1551 | 1552 | # Count and print only tags 1553 | if args.special == "tag_count": 1554 | 1555 | tag_count = {} 1556 | 1557 | for row in results: 1558 | for entry in row: 1559 | tags = entry.split(",") 1560 | 1561 | for tag in tags: 1562 | if tag not in tag_count: 1563 | tag_count[tag] = 1 1564 | else: 1565 | tag_count[tag] += 1 1566 | 1567 | for tag in tag_count: 1568 | message_proc("%05s | %s" % (tag_count[tag], tag), args) 1569 | 1570 | elif args.special == "count": 1571 | 1572 | row_count = {} 1573 | 1574 | for row in results: 1575 | entry = " , ".join(row) 1576 | if entry not in row_count: 1577 | row_count[entry] = 1 1578 | else: 1579 | row_count[entry] += 1 1580 | 1581 | widths = [max(list(map(len, col))) for col in zip(*results)] 1582 | for row in row_count: 1583 | new_row = row.split(",") 1584 | new_row = " | ".join((val.ljust(width) for val, width in zip(new_row, widths))) 1585 | message_proc("%05s | %s" % (row_count[row], new_row), args) 1586 | 1587 | else: 1588 | # Auto-adjust column widths 1589 | widths = [max(list(map(len,col))) for col in zip(*results)] 1590 | for row in results: 1591 | message_proc(" | ".join((val.ljust(width) for val, width in zip(row, widths))), args) 1592 | 1593 | if not args.quiet: 1594 | message_proc("\n[+] processed %s samples [+]\n" % str(count), args) 1595 | 1596 | # Session output 1597 | if args.run == "session_scrape": 1598 | 1599 | results = session_scrape(args) 1600 | 1601 | if args.special == "count": 1602 | 1603 | row_count = {} 1604 | 1605 | for row in results: 1606 | entry = " , ".join(row) 1607 | if entry not in row_count: 1608 | row_count[entry] = 1 1609 | else: 1610 | row_count[entry] += 1 1611 | 1612 | widths = [max(list(map(len, col))) for col in zip(*results)] 1613 | for row in row_count: 1614 | new_row = row.split(",") 1615 | new_row = " | ".join((val.ljust(width) for val, width in zip(new_row, widths))) 1616 | message_proc("%05s | %s" % (row_count[row], new_row), args) 1617 | 1618 | else: 1619 | 1620 | # Auto-adjust column widths 1621 | widths = [max(list(map(len,col))) for col in zip(*results)] 1622 | for row in results: 1623 | message_proc(" | ".join((val.ljust(width) for val, width in zip(row, widths))), args) 1624 | 1625 | if not args.quiet: 1626 | message_proc("\n[+] processed %s sessions [+]\n" % str(count), args) 1627 | 1628 | 1629 | # AutoFocus Import Function 1630 | # Builds a query for import into AutoFocus based on returned results 1631 | # AutoFocus API has a limit on the lines allowed and too many results will make it more challenging to manage in the portal 1632 | 1633 | def af_import(args, sample_data): 1634 | 1635 | # Initialize some values 1636 | output = args.output 1637 | 1638 | if "all" in output: 1639 | output = [] 1640 | for key in list(sample_data.keys()): 1641 | output.append(key) 1642 | 1643 | # Build AutoFocus query 1644 | if not args.quiet: 1645 | message_proc("[+] af import query [+]\n", args) 1646 | 1647 | import_query = '{"operator":"all","children":[' 1648 | for entry in output: 1649 | if entry in list(sample_data.keys()) and entry == "dns": 1650 | for value in sample_data[entry]: 1651 | import_query += '{"field":"sample.tasks.dns","operator":"contains","value":"' + value + '"},' 1652 | if entry in list(sample_data.keys()) and entry == "http": 1653 | for value in sample_data[entry]: 1654 | import_query += '{"field":"sample.tasks.http","operator":"contains","value":"' + value + '"},' 1655 | if entry in list(sample_data.keys()) and entry == "connection": 1656 | for value in sample_data[entry]: 1657 | value_split = value.split(" , ") 1658 | for subvalue in value_split: # Instead of trying to parse all of the different formats for connection, just include IP:DPORT 1659 | if ":" in subvalue: 1660 | import_query += '{"field":"sample.tasks.connection","operator":"contains","value":"' + subvalue + '"},' 1661 | if entry in list(sample_data.keys()) and entry == "user_agent": 1662 | for value in sample_data[entry]: 1663 | import_query += '{"field":"sample.tasks.user_agent","operator":"is","value":"' + value + '"},' 1664 | if entry in list(sample_data.keys()) and entry == "mutex": 1665 | for value in sample_data[entry]: 1666 | import_query += '{"field":"sample.tasks.mutex","operator":"contains","value":"' + value + '"},' 1667 | if entry in list(sample_data.keys()) and entry == "process": 1668 | for value in sample_data[entry]: 1669 | import_query += '{"field":"sample.tasks.process","operator":"contains","value":"' + value.replace("\"", "\\\"") + '"},' 1670 | if entry in list(sample_data.keys()) and entry == "file": 1671 | for value in sample_data[entry]: 1672 | import_query += '{"field":"sample.tasks.file","operator":"contains","value":"' + value + '"},' 1673 | if entry in list(sample_data.keys()) and entry == "registry": 1674 | for value in sample_data[entry]: 1675 | import_query += '{"field":"sample.tasks.registry","operator":"contains","value":"' + value + '"},' 1676 | if entry in list(sample_data.keys()) and entry == "service": 1677 | for value in sample_data[entry]: 1678 | import_query += '{"field":"sample.tasks.service","operator":"contains","value":"' + value + '"},' 1679 | 1680 | import_query += ']}' 1681 | import_query = import_query[:len(import_query) - 3] + import_query[-2:] 1682 | import_query = str(import_query.replace("\\", "\\\\")) # Double escape for AF 1683 | import_query = import_query.replace("\\\\\"", "\\\"") 1684 | 1685 | message_proc("%s\n" % import_query, args) 1686 | 1687 | 1688 | # Yara Rule Function 1689 | # Attempts to take the likely data you might find from dynamic analysis and build a yara rule for memory process scanning using volatility/other tools 1690 | # Some sections commented out as they generate far too many entries/false positives that haven't been programatically filtered 1691 | 1692 | def yara_rule(args, sample_data): 1693 | 1694 | # Initialize some values 1695 | output = args.output 1696 | 1697 | if "all" in output: 1698 | output = [] 1699 | for key in list(sample_data.keys()): 1700 | output.append(key) 1701 | 1702 | if not args.quiet: 1703 | message_proc("[+] yara rule [+]\n", args) 1704 | 1705 | min_len = 4 # Minimum string length 1706 | contained_list = [] 1707 | entry_list = [] 1708 | 1709 | # Build yara rule 1710 | yara_sig = "rule autogen_afLenz\n{\n\t// %s\n\n\tstrings:\n" % args 1711 | 1712 | for entry in output: 1713 | if entry in list(sample_data.keys()) and entry == "dns": 1714 | count = 0 1715 | 1716 | if args.run == "dns_scrape": 1717 | for value in sample_data[entry]: 1718 | if value not in contained_list and value != "" and len(value) > min_len: 1719 | entry_list.append("dns") 1720 | contained_list.append(value) 1721 | yara_sig += "\t\t$dns_" + str(count) + " = \"" + value + "\" wide ascii\n" # Just grab the domain 1722 | count += 1 1723 | else: 1724 | for value in sample_data[entry]: 1725 | dns_entry = value.split(" , ")[0] 1726 | dns_resolve = value.split(" , ")[1] 1727 | if dns_entry not in contained_list and dns_entry != "" and len(dns_entry) > min_len: 1728 | entry_list.append("dns") 1729 | contained_list.append(dns_entry) 1730 | yara_sig += "\t\t$dns_" + str(count) + " = \"" + dns_entry + "\" wide ascii\n" # Just grab the domain 1731 | if dns_resolve not in contained_list and dns_resolve != "" and len(dns_resolve) > min_len: 1732 | entry_list.append("dns") 1733 | contained_list.append(dns_resolve) 1734 | yara_sig += "\t\t$dns_" + str(count+1) + " = \"" + dns_resolve + "\" wide ascii\n" # Just grab the resolved IP 1735 | count += 2 1736 | 1737 | if entry in list(sample_data.keys()) and entry == "http": 1738 | count = 0 1739 | if args.run == "http_scrape": 1740 | for value in sample_data[entry]: 1741 | value = value.replace("hxxp", "http") 1742 | if value not in contained_list and value != "" and len(value) > min_len: 1743 | entry_list.append("http") 1744 | contained_list.append(value) 1745 | yara_sig += "\t\t$http_" + str(count) + " = \"" + value + "\" wide ascii\n" # Just grab the domain 1746 | count += 1 1747 | else: 1748 | for value in sample_data[entry]: 1749 | domain_name = value.split(" , ")[0] 1750 | try: 1751 | url_path = value.split(" , ")[2] 1752 | except: 1753 | url_path = "" 1754 | try: 1755 | full_ua = value.split(" , ")[3] 1756 | except: 1757 | full_ua = "" 1758 | if domain_name not in contained_list and domain_name != "" and len(domain_name) > min_len: 1759 | entry_list.append("http") 1760 | contained_list.append(domain_name) 1761 | yara_sig += "\t\t$http_" + str(count) + " = \"" + domain_name + "\" wide ascii\n" # Just grab the domain 1762 | if url_path not in contained_list and url_path != "" and len(url_path) > min_len: 1763 | entry_list.append("http") 1764 | contained_list.append(url_path) 1765 | yara_sig += "\t\t$http_" + str(count+1) + " = \"" + url_path + "\" wide ascii\n" # Just grab the URL path 1766 | if full_ua not in contained_list and full_ua != "" and len(full_ua) > min_len: 1767 | entry_list.append("http") 1768 | contained_list.append(full_ua) 1769 | yara_sig += "\t\t$http_" + str(count+2) + " = \"" + full_ua + "\" wide ascii\n" # Just grab the full user-agent 1770 | count += 3 1771 | 1772 | if entry in list(sample_data.keys()) and entry == "connection": 1773 | count = 0 1774 | for value in sample_data[entry]: 1775 | value_split = value.split(" , ") 1776 | for subvalue in value_split: 1777 | if ":" in subvalue and subvalue not in contained_list and len(subvalue) > min_len: 1778 | entry_list.append("connection") 1779 | contained_list.append(subvalue.split(":")[0]) 1780 | yara_sig += "\t\t$connection_" + str(count) + " = \"" + subvalue.split(":")[0] + "\"\n" # Just grab IP 1781 | count += 1 1782 | 1783 | if entry in list(sample_data.keys()) and entry == "user_agent": 1784 | count = 0 1785 | for value in sample_data[entry]: 1786 | if value not in contained_list and value != "" and len(value) > min_len: 1787 | entry_list.append("user_agent") 1788 | contained_list.append(value) 1789 | yara_sig += "\t\t$user_agent_" + str(count) + " = \"" + value + "\"\n" # Just grab the UA fragment 1790 | count += 1 1791 | 1792 | if entry in list(sample_data.keys()) and entry == "mutex": 1793 | mutex_blacklist = ["Local\!IETld!Mutex", 1794 | "IESQMMUTEX_0_208", 1795 | "c:!documents and settings!administrator!local settings!temporary internet files!content.ie5!", 1796 | "c:!documents and settings!administrator!cookies!", 1797 | "c:!documents and settings!administrator!local settings!history!history.ie5!", 1798 | "WininetConnectionMutex", 1799 | ""] # Entries to ignore 1800 | count = 0 1801 | if args.run == "mutex_scrape": 1802 | for mutex in sample_data[entry]: 1803 | if mutex not in contained_list and mutex != "" and mutex not in mutex_blacklist and len(mutex) > min_len: 1804 | entry_list.append("mutex") 1805 | contained_list.append(mutex) 1806 | yara_sig += "\t\t$mutex_" + str(count) + " = \"" + mutex + "\"\n" # Just grab the domain 1807 | count += 1 1808 | else: 1809 | for mutex in sample_data[entry]: 1810 | mutex = mutex.split(" , ")[2] 1811 | if mutex not in contained_list and mutex != "" and mutex not in mutex_blacklist and len(mutex) > min_len: # Just grab mutex name 1812 | entry_list.append("mutex") 1813 | contained_list.append(mutex) 1814 | yara_sig += "\t\t$mutex_" + str(count) + " = \"" + mutex + "\"\n" 1815 | count += 1 1816 | # 1817 | # The below are commented out simply because they generate a LOT of data and noise. Uncomment if necessary. 1818 | # 1819 | #if entry in sample_data.keys() and entry == "process": 1820 | # count = 0 1821 | # for value in sample_data[entry]: 1822 | # entry_list.append("process") 1823 | # yara_sig += "\t\t$process_" + str(count) + " = \"" + value + "\"\n" # A bit too noisy and FP prone 1824 | # count += 1 1825 | #if entry in sample_data.keys() and entry == "file": 1826 | # count = 0 1827 | # for value in sample_data[entry]: 1828 | # file_name = value.split(" , ")[2].strip("C:\\") 1829 | # if file_name not in contained_list and file_name != "" and len(file_name) > min_len: 1830 | # entry_list.append("file") 1831 | # contained_list.append(file_name) 1832 | # yara_sig += "\t\t$file_" + str(count) + " = \"" + file_name + "\"\n" # Just grab file path/name 1833 | # count += 1 1834 | #if entry in sample_data.keys() and entry == "registry": 1835 | # count = 0 1836 | # for value in sample_data[entry]: 1837 | # registry_key = value.split(" , ")[2].split(",")[0] 1838 | # if registry_key not in contained_list and registry_key != "" and "\\" in registry_key and len(registry_key) > min_len: 1839 | # entry_list.append("registry") 1840 | # contained_list.append(registry_key) 1841 | # yara_sig += "\t\t$registry_" + str(count) + " = \"" + registry_key + "\"\n" 1842 | # count += 1 1843 | 1844 | entry_list = list(set(entry_list)) 1845 | 1846 | yara_sig += "\n\tcondition:\n\t\t1 of (" + ", ".join(["$" + value + "*" for value in entry_list]) + ") /* Adjust as needed for accuracy */\n}" 1847 | yara_sig = str(yara_sig.replace("\\", "\\\\")) # Double escape for yara 1848 | 1849 | if "$" in yara_sig: 1850 | message_proc("%s\n" % yara_sig, args) 1851 | else: 1852 | message_proc("No yara rule could be generated.\n") 1853 | 1854 | 1855 | ################ 1856 | # MAIN PROGRAM # 1857 | ################ 1858 | 1859 | def main(): 1860 | 1861 | # Set initial values 1862 | functions = [ 1863 | "uniq_sessions", 1864 | "common_artifacts", 1865 | "common_pieces", 1866 | "hash_scrape", 1867 | "http_scrape", 1868 | "dns_scrape", 1869 | "mutex_scrape", 1870 | "meta_scrape", 1871 | "sample_scrape", 1872 | "service_scrape", 1873 | "session_scrape", 1874 | "diff", 1875 | "tag_check", 1876 | "tag_info", 1877 | "dropped_file_scrape", 1878 | "coverage_scrape" 1879 | ] 1880 | session_sections = [ 1881 | "account_name", 1882 | "application", 1883 | "device_country_code", 1884 | "device_country", 1885 | "device_hostname", 1886 | "industry", 1887 | "business_line", 1888 | "device_model", 1889 | "device_serial", 1890 | "device_version", 1891 | "dst_country_code", 1892 | "dst_country", 1893 | "dst_ip", 1894 | "dst_is_private_ip" 1895 | "dst_port", 1896 | "email_recipient", 1897 | "email_charset", 1898 | "email_sender", 1899 | "email_subject", 1900 | "file_name", 1901 | "file_url", 1902 | "sha256", 1903 | "src_country_code", 1904 | "src_country", 1905 | "src_ip", 1906 | "src_is_private_ip" 1907 | "src_port", 1908 | "timestamp", 1909 | "upload_source", 1910 | "user_id", 1911 | "_vsys" 1912 | ] 1913 | sample_sections = [ 1914 | "apk_app_icon", 1915 | "apk_app_name", 1916 | "apk_cert_file", 1917 | "apk_certificate_id", # Unused currently 1918 | "apk_defined_activity", 1919 | "apk_defined_intent_filter", 1920 | "apk_defined_receiver", 1921 | "apk_defined_sensor", 1922 | "apk_defined_service", 1923 | "apk_digital_signer", 1924 | "apk_embedded_library", 1925 | "apk_embeded_url", 1926 | "apk_internal_file", 1927 | "apk_isrepackaged", 1928 | "apk_packagename", 1929 | "apk_requested_permission", 1930 | "apk_sensitive_api_call", 1931 | "apk_suspicious_action_monitored", 1932 | "apk_suspicious_api_call", 1933 | "apk_suspicious_file", 1934 | "apk_suspicious_pattern", 1935 | "apk_suspicious_string", 1936 | "apk_version_num", 1937 | "behavior", 1938 | "behavior_type", 1939 | "connection", 1940 | "default", 1941 | "digital_signer", 1942 | "dns", 1943 | "dropped_files", 1944 | "elf_commands", 1945 | "elf_command_action", 1946 | "elf_domains", 1947 | "elf_file_activity", 1948 | "elf_file_paths", 1949 | "elf_functions", 1950 | "elf_ip_address", 1951 | "elf_suspicious_action", 1952 | "elf_suspicious_behavior", 1953 | "elf_urls", 1954 | "file", 1955 | "http", 1956 | "imphash", 1957 | "japi", 1958 | "mac_embedded_file", 1959 | "mac_embedded_url", 1960 | "macro", 1961 | "misc", 1962 | "mutex", 1963 | "process", 1964 | "registry", 1965 | "service", 1966 | "summary", 1967 | "user_agent" 1968 | ] 1969 | coverage_sections = [ 1970 | "dns_sig", 1971 | "fileurl_sig", 1972 | "url_cat", 1973 | "wf_av_sig" 1974 | ] 1975 | meta_sections = [ 1976 | "sha256", 1977 | "file_type", 1978 | "create_date", 1979 | "verdict", 1980 | "file_size", 1981 | "tags", 1982 | "sha1", 1983 | "md5", 1984 | "ssdeep", 1985 | "imphash", 1986 | "digital_signer" 1987 | ] 1988 | identifiers = [ 1989 | "connection", 1990 | "dns", 1991 | "email", 1992 | "file", 1993 | "file_hashes", 1994 | "file_query", 1995 | "filename", 1996 | "fileurl", 1997 | "hash", 1998 | "hash_list", 1999 | "http", 2000 | "ip", 2001 | "mutex", 2002 | "process", 2003 | "query", 2004 | "registry", 2005 | "service", 2006 | "tag", 2007 | "threat", 2008 | "url", 2009 | "user_agent" 2010 | ] 2011 | specials = [ 2012 | "af_import", 2013 | "bgm", 2014 | "count", 2015 | "range", 2016 | "tag_count", 2017 | "yara_rule" 2018 | ] 2019 | filter = [ 2020 | "all_suspicious", 2021 | "highly_suspicious", 2022 | "suspicious" 2023 | ] 2024 | platforms = [ 2025 | "android", 2026 | "mac", 2027 | "staticAnalyzer", 2028 | "win10", 2029 | "win7", 2030 | "winxp" 2031 | ] 2032 | 2033 | # Grab initial arguments from CLI 2034 | parser = argparse.ArgumentParser(description="Run functions to retrieve information from AutoFocus.") 2035 | parser.add_argument("-i", "--ident", help="Query identifier type for AutoFocus search. [" + ", ".join(identifiers) + "]", metavar="", required=True) 2036 | parser.add_argument("-q", "--query", help="Value to query Autofocus for.", metavar='', required=True) 2037 | parser.add_argument("-o", "--output", help="Section of data to return. Multiple values are comma separated (no space) or \"all\" for everything, which is default. " 2038 | "Sample Sections [" + ", ".join(sample_sections) + "]. " 2039 | "Session Sections [" + ", ".join(session_sections) + "]. " 2040 | "Meta Sections [" + ", ".join(meta_sections) + "]. " 2041 | "Coverage Sections [" + ", ".join(coverage_sections) + "]. " , metavar="", default="all") 2042 | parser.add_argument("-f", "--filter", help="Filter out Benign/Grayware/Malware counts over this number, default 10,000. Uses pre-built malware filtering from AF. Use 0 for no filter. [" + ", ".join(filter) + "]", metavar="", default=10000) 2043 | parser.add_argument("-l", "--limit", help="Limit the number of analyzed samples, default 200. Use 0 for no limit.", metavar="", type=int, default=200) 2044 | parser.add_argument("-r", "--run", choices=functions, help="Function to run. [" + ", ".join(functions) + "]", metavar='', required=True) 2045 | parser.add_argument("-s", "--special", choices=specials, help="Output data formated in a special way for other tools. [" + ", ".join(specials) + "]", metavar="",default=[]) 2046 | parser.add_argument("-c", "--commonality", help="Commonality percentage for comparison functions, default is 100", metavar="", type=int, default=100) 2047 | parser.add_argument("-Q", "--quiet",help="Suppress any additional informational output and only return specified data.",action="store_true",default=False) 2048 | parser.add_argument("-w", "--write", help="Write output to a file instead of STDOUT.", metavar="", default=False) 2049 | parser.add_argument("-d", "--debug", help="Enable debug logging (limited usage).", action="store_true") 2050 | parser.add_argument("-p", "--platform", help="Limit results to the specific VM platform. [" + ", ".join(platforms) + "]", metavar="", default="all") 2051 | args = parser.parse_args() 2052 | args.query = args.query.replace("\\", "\\\\") 2053 | 2054 | # Setup logging (separate) 2055 | if args.debug: 2056 | logging.basicConfig(level = logging.INFO, 2057 | format = "%(asctime)s %(levelname)-8s %(message)s", 2058 | datefmt = "%Y-%m-%d %H:%M:%S", 2059 | stream = sys.stdout) 2060 | else: 2061 | logging.basicConfig(level = logging.ERROR, 2062 | format = "%(asctime)s %(levelname)-8s %(message)s", 2063 | datefmt = "%Y-%m-%d %H:%M:%S", 2064 | stream = sys.stdout) 2065 | 2066 | # Extrapolate sections 2067 | sections = [] 2068 | if args.output == "all_apk" or args.output == "all_elf": 2069 | for section in sample_sections: 2070 | if section.startswith(args.output.split("_")[1]): 2071 | sections.append(section) 2072 | elif args.output == "all": 2073 | sections.append("all") 2074 | else: 2075 | for section in args.output.split(","): 2076 | if section in sample_sections or \ 2077 | section in meta_sections or \ 2078 | section in session_sections or \ 2079 | section in coverage_sections: 2080 | sections.append(section) 2081 | 2082 | args.output = sections 2083 | 2084 | 2085 | if args.ident == "file_hashes": 2086 | hashlist = fetch_from_file(args, args.query) 2087 | # Build an AF query using the hash list that was just generated join the list into a comma-separated string, because this is what some other functions expect. 2088 | args.query = af_query("hash_list",",".join(item for item in hashlist)) 2089 | args.ident = "query" 2090 | 2091 | elif args.ident == "file_query": 2092 | # Build an AF query using input from a file - helpful for when quotes on CLI are not escaped the same 2093 | args.query = fetch_from_file(args, args.query) 2094 | args.ident = "query" 2095 | 2096 | # No filter limit 2097 | if args.filter == "0": # String due to keyword usage 2098 | args.filter = 1000000000 2099 | 2100 | # No sample/session limit 2101 | if args.limit == 0: 2102 | args.limit = 1000000000 2103 | 2104 | # Gather results from functions 2105 | funct_type = "sample" 2106 | 2107 | if not args.quiet: 2108 | if args.ident == "query": 2109 | message_proc("\n%s" % args.query, args) 2110 | elif args.run == "tag_check": 2111 | message_proc("\nTag: %s\nHash: %s" % (args.query.split(",")[0], args.query.split(",")[1]), args) 2112 | else: 2113 | message_proc("\n%s" % af_query(args.ident, args.query).strip(), args) 2114 | 2115 | if args.run == "uniq_sessions": 2116 | out_data = uniq_sessions(args) 2117 | funct_type = "session" 2118 | elif args.run == "hash_scrape" or args.run == "coverage_scrape" or args.run == "sample_scrape": 2119 | out_data = hash_scrape(args) 2120 | elif args.run == "common_artifacts": 2121 | out_data = common_artifacts(args) 2122 | elif args.run == "common_pieces": 2123 | out_data = common_pieces(args) 2124 | elif args.run == "http_scrape": 2125 | out_data = http_scrape(args) 2126 | elif args.run == "dns_scrape": 2127 | out_data = dns_scrape(args) 2128 | elif args.run == "mutex_scrape": 2129 | out_data = mutex_scrape(args) 2130 | elif args.run == "service_scrape": 2131 | out_data = service_scrape(args) 2132 | elif args.run == "meta_scrape" or args.run == "session_scrape": 2133 | out_data = {} 2134 | funct_type = "list" 2135 | elif args.run == "diff": 2136 | out_data = diff(args) 2137 | elif args.run == "tag_check": 2138 | out_data = tag_check(args) 2139 | elif args.run == "tag_info": 2140 | tag_info(args) 2141 | elif args.run == "dropped_file_scrape": 2142 | out_data = dropped_file_scrape(args) 2143 | 2144 | if "count" not in out_data: 2145 | out_data['count'] = 1 2146 | 2147 | # If we have specified a -s option, do the following 2148 | if "af_import" in args.special or "yara_rule" in args.special: 2149 | 2150 | if args.run == "meta_scrape" or args.run == "session_scrape": 2151 | output_list(args) 2152 | else: 2153 | output_analysis(args, out_data, funct_type) 2154 | 2155 | if "af_import" in args.special: 2156 | af_import(args, out_data) 2157 | if "yara_rule" in args.special: 2158 | yara_rule(args, out_data) 2159 | 2160 | else: 2161 | if args.run == "meta_scrape" or args.run == "session_scrape": 2162 | output_list(args) 2163 | else: 2164 | output_analysis(args, out_data, funct_type) 2165 | 2166 | if __name__ == '__main__': 2167 | main() 2168 | 2169 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import sys 4 | from setuptools import setup, find_packages 5 | 6 | NAME = "af_lenz" 7 | VERSION = "1.3.6" 8 | 9 | REQUIRES = ["autofocus-client-library", "requests"] 10 | 11 | setup( 12 | name=NAME, 13 | version=VERSION, 14 | description="Autofocus Lenz", 15 | author_email="jwhite@paloaltonetworks.com", 16 | url="", 17 | keywords=["autofocus", "af_lenz"], 18 | install_requires=REQUIRES, 19 | packages=find_packages(), 20 | py_modules=['af_lenz'], 21 | include_package_data=True, 22 | long_description="A tool for interfacing with the PANW autofocus api", 23 | python_requires = ">=3.4", 24 | entry_points = { 25 | 'console_scripts': ['af_lenz=af_lenz:main'] 26 | } 27 | ) 28 | --------------------------------------------------------------------------------