├── .gitignore ├── LICENSE ├── tsr.sln ├── tsr.cpp ├── README.md └── tsr.vcxproj /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Scott Lundgren 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tsr.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.17 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tsr", "tsr.vcxproj", "{3CF35CF3-9232-4CD0-881C-C18E9113475B}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {3CF35CF3-9232-4CD0-881C-C18E9113475B}.Debug|x64.ActiveCfg = Debug|x64 17 | {3CF35CF3-9232-4CD0-881C-C18E9113475B}.Debug|x64.Build.0 = Debug|x64 18 | {3CF35CF3-9232-4CD0-881C-C18E9113475B}.Debug|x86.ActiveCfg = Debug|Win32 19 | {3CF35CF3-9232-4CD0-881C-C18E9113475B}.Debug|x86.Build.0 = Debug|Win32 20 | {3CF35CF3-9232-4CD0-881C-C18E9113475B}.Release|x64.ActiveCfg = Release|x64 21 | {3CF35CF3-9232-4CD0-881C-C18E9113475B}.Release|x64.Build.0 = Release|x64 22 | {3CF35CF3-9232-4CD0-881C-C18E9113475B}.Release|x86.ActiveCfg = Release|Win32 23 | {3CF35CF3-9232-4CD0-881C-C18E9113475B}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /tsr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | VOID NTAPI TerminateAndStayResidentProc(PVOID, BOOLEAN) 5 | { 6 | printf("hi!\n"); 7 | 8 | OutputDebugStringA("Still here!\n"); 9 | 10 | // obvious things to do here: 11 | // - check for existance of a file in a hardcoded path and, if present, execute it 12 | // - check for existance of a section object and, if present, map it in and execute it 13 | // - check for pending incoming connection on a pre-created SOCKET and accept the connection... 14 | // - check for event being set and, if so, self-terminate the timer queue 15 | // - check for registry value and, if present, read file or shellcode and execute it 16 | // - check for system shutdown notification and, if shutting down, persist to disk 17 | } 18 | 19 | int wmain(int argc, WCHAR **argv) 20 | { 21 | HRESULT hr = E_UNEXPECTED; 22 | HANDLE hTimerQueue = NULL, 23 | hTimer = NULL; 24 | 25 | hTimerQueue = CreateTimerQueue(); 26 | if (NULL == hTimerQueue) 27 | { 28 | goto ErrorExit; 29 | } 30 | 31 | if (!CreateTimerQueueTimer(&hTimer, hTimerQueue, TerminateAndStayResidentProc, NULL, 1000, 1000, 0)) 32 | { 33 | hr = HRESULT_FROM_WIN32(GetLastError()); 34 | goto ErrorExit; 35 | } 36 | 37 | Sleep(INFINITE); 38 | 39 | hr = S_OK; 40 | 41 | ErrorExit: 42 | 43 | if (NULL != hTimer) 44 | { 45 | (void)DeleteTimerQueueTimer(hTimerQueue, hTimer, INVALID_HANDLE_VALUE); 46 | } 47 | 48 | if (NULL != hTimerQueue) 49 | { 50 | (void)DeleteTimerQueueEx(hTimerQueue, INVALID_HANDLE_VALUE); 51 | } 52 | 53 | return 0; 54 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tsr 2 | Timer and Stay Resident 3 | 4 | From an attacker perspective, the capability to maintain persistence is very powerful. There are many known (and certainly many unreported or unknown!) techniques to maintain persistence over reboots. Mitre has one list of techniques as part of the ATT&CK framework at https://attack.mitre.org/wiki/Persistence 5 | 6 | There is another sort of persistence though - the idea of being able to maintain execution within (as opposed to across) boot sessions but do so in a manner that is difficult to detect. In the case that the goal is to maintain _access_ to the host but without actively doing work, the attacker has the challenge of having a 7 | 8 | There are all manner of ways to achieve this effect. The most straightforward is to simply create a long-running process backed by an executable created by the attacker. This is not stealthy - everything from anti-virus to forensic analysis to simply looking at the Windows Task Manager is sufficient to identify the rogue process. 9 | 10 | Another logical approach is to inject code into a process and then create a thread backed by this code. This is useful in that there is no standalone process and so is a lot quieter than the first approach. This approach can discovered by enumerating the thread stacks of all running threads. Threads with functions not backed by PEs (.exes, .dlls, etc.) in the thread stack are suspicious in this way. 11 | 12 | As a thought experiment, I set out to enumerate a set of ways to maintain in-memory usermode persistence without any objects on disk and any references to my code in any call stacks. It did not take long to discover quite a few viable paths. 13 | 14 | One of these paths is to use the Windows Timer Queue. The Timer Queue sets up a thread pool and then can be configured to execute an arbitrary callback function on an arbitrary interval. By using the Windows thread pool, the call stacks of all threads are entirely within standard Windows DLLs until the moment the timer fires. The callback can then quickly check to see if some condition holds (an event is signalled, a file exists on disk, a socket has a pending incoming connection attempt, etc.). If the condition holds, it can act on that. If not, it can return and keep the call stacks clean. 15 | 16 | 17 | This is a mildly interesting use of a very old technique - rootkits are often built on this generic approach. For those of you old enough to remember, in the days of DOS there were "Terminate and Stay Resident" or TSRs. Simply because "Timer" starts with a T, I call this approach "Timer and Stay Resident". 18 | -------------------------------------------------------------------------------- /tsr.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {3CF35CF3-9232-4CD0-881C-C18E9113475B} 24 | Win32Proj 25 | tsr 26 | 10.0.16299.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v141 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v141 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v141 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v141 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | true 78 | 79 | 80 | false 81 | 82 | 83 | false 84 | 85 | 86 | 87 | 88 | 89 | Level3 90 | Disabled 91 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 92 | 93 | 94 | Console 95 | 96 | 97 | 98 | 99 | 100 | 101 | Level3 102 | Disabled 103 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 104 | 105 | 106 | Console 107 | 108 | 109 | 110 | 111 | Level3 112 | 113 | 114 | MaxSpeed 115 | true 116 | true 117 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 118 | 119 | 120 | Console 121 | true 122 | true 123 | 124 | 125 | 126 | 127 | Level3 128 | 129 | 130 | MaxSpeed 131 | true 132 | true 133 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 134 | 135 | 136 | Console 137 | true 138 | true 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | --------------------------------------------------------------------------------