Update: Since this post is getting some international attention I want to use the chance: If you are into Threat Hunting and interested in collaboration: Contact me and consider working on the ThreatHunter-Playbook! :) /Update

The art of hunting mimikatz with sysmons EventID 10 got already published by @cyb3rward0g in his great blog: Chronicles of a Threat Hunter: Hunting for In-Memory Mimikatz with Sysmon and ELK - Part II (Event ID 10). He also published the ThreatHunter Playbook which is a great collection of Windows Events you can use to hunt intruders in your network. I will shortly set up a GitHub Pull request to the playbook, maybe my findings are interesting for the community. :)

From there I today invested some time to analyze mimikatz to extract all uses of OpenProcess() and therefore some more indicators to hunt mimikatz. To achive that I first created a caller graph for OpenProcess() using the whole mimikatz source tree:

caller graph OpenProcess()

Update: I used mimikatz 2.1.1, which i checked out and build on April, 1st 2017. /Update

From there on I walked through the callers of OpenProcess() and extracted the following values (bitmask, opened process, module and command). To ease my work a little bit I wrote a small helper binary calc_mask which simply calculates the bitmask to the corresponding string:

$ ./calc_mask -m "PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION |
PROCESS_QUERY_INFORMATION"
0x1438

It's written in go, it is free but not yet complete. Get it here

From there on it was just some routine piece of work. Please note the comments in the last column.

  • My most interesting finding is the behaviour of mimikatzs command event::drop, which patches the Eventlog process to drop all received events - even the event which is necessary to patch the Eventlog process (!). This might be some kind of race condition ...

  • The Events, especially the amount, generated by the token::* commands is also noteworthy. These commands result in a huge number of OpenProcess() calls which should be pretty easy to monitor - see the comment column.

Todos:

  • OpenProcessToken()
  • ...

In the future I might post some elaboration about my findings, but for now: to the data...

module OpenProcess caller function destination process / destination service ACCESS_MASK ACCESS_MASK translated comment
lsadump::lsa /patch kuhl_m_lsadump_lsa_getHandle() SamSs PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION 0x1438
lsadump::lsa /inject kuhl_m_lsadump_lsa_getHandle() SamSs PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD 0x143a
lsadump::trust /patch kuhl_m_lsadump_lsa_getHandle() SamSs PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION 0x1438
minesweeper::infos kuhl_m_minesweeper_infos() minesweeper.exe PROCESS_VM_READ | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION 0x1418
misc:detours kuhl_m_misc_detours_callback_process() * GENERIC_READ omitted because of the very generic ACCESS_MASK
misc:memssp kuhl_m_misc_memssp() lsass.exe PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION 0x1438
misc:skeleton kuhl_m_misc_skeleton() lsass.exe PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION 0x1438
process::suspend, process:stop, process:resume,process:imports, process:exports kuhl_m_process_genericOperation() omitted because of the very generic ACCESS_MASKs
vault::cred /patch kuhl_m_vault_cred() SamSs PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION 0x1438
sekurlsa::* kuhl_m_sekurlsa_acquireLSA() lsass.exe PROCESS_VM_READ | PROCESS_QUERY_INFORMATION 0x1410 for Windows Version < 5
sekurlsa::* kuhl_m_sekurlsa_acquireLSA() lsass.exe PROCESS_VM_READ | PROCESS_QUERY_LIMITED_INFORMATION 0x1010 for Windows Version >= 6
token::list, token::elevate, token::run querying all processes on the system * first 0x1400 then 0x40 all three commands result in a call to kull_m_token_getTokens() which first iterates over all processes and threads with OpenProcess(PROCESS_QUERY_INFORMATION (0x1400)) (kull_m_token_getTokens_process_callback()) and then again to get the tokens OpenProcess(PROCESS_DUP_HANDLE (0x40)) (in kull_m_handle_getHandlesOfType_callback()) to duplicate the Tokens. This resultet in many thousand (!) Events with ID 10 (!)
crypto::cng kull_m_patch_genericProcessOrServiceFromBuild() via kuhl_m_crypto_p_cng() KeyIso PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION 0x1438
event::drop kull_m_patch_genericProcessOrServiceFromBuild() via kuhl_m_event_drop() EventLog PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION 0x1438 this event does not get logged! :O mimikatz seems to be fast enough to apply the patch before the event gets logged!
misc::ncroutemon kull_m_patch_genericProcessOrServiceFromBuild() via kuhl_m_misc_ncroutemon() dsNcService PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION 0x1438
ts::multirdp kull_m_patch_genericProcessOrServiceFromBuild() via kuhl_m_ts_multirdp() TermService PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION 0x1438