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:
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.
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|