├── .gitignore ├── README.md ├── main.cpp ├── ntinfo.cpp ├── ntinfo.h ├── threadstack.filters ├── threadstack.sln └── threadstack.vcxproj /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | 154 | # Windows Azure Build Output 155 | csx/ 156 | *.build.csdef 157 | 158 | # Windows Azure Emulator 159 | efc/ 160 | rfc/ 161 | 162 | # Windows Store app package directory 163 | AppPackages/ 164 | 165 | # Visual Studio cache files 166 | # files ending in .cache can be ignored 167 | *.[Cc]ache 168 | # but keep track of directories ending in .cache 169 | !*.[Cc]ache/ 170 | 171 | # Others 172 | ClientBin/ 173 | [Ss]tyle[Cc]op.* 174 | ~$* 175 | *~ 176 | *.dbmdl 177 | *.dbproj.schemaview 178 | *.pfx 179 | *.publishsettings 180 | node_modules/ 181 | orleans.codegen.cs 182 | 183 | # RIA/Silverlight projects 184 | Generated_Code/ 185 | 186 | # Backup & report files from converting an old project file 187 | # to a newer Visual Studio version. Backup files are not needed, 188 | # because we have git ;-) 189 | _UpgradeReport_Files/ 190 | Backup*/ 191 | UpgradeLog*.XML 192 | UpgradeLog*.htm 193 | 194 | # SQL Server files 195 | *.mdf 196 | *.ldf 197 | 198 | # Business Intelligence projects 199 | *.rdl.data 200 | *.bim.layout 201 | *.bim_*.settings 202 | 203 | # Microsoft Fakes 204 | FakesAssemblies/ 205 | 206 | # GhostDoc plugin setting file 207 | *.GhostDoc.xml 208 | 209 | # Node.js Tools for Visual Studio 210 | .ntvs_analysis.dat 211 | 212 | # Visual Studio 6 build log 213 | *.plg 214 | 215 | # Visual Studio 6 workspace options file 216 | *.opt 217 | 218 | # Visual Studio LightSwitch build output 219 | **/*.HTMLClient/GeneratedArtifacts 220 | **/*.DesktopClient/GeneratedArtifacts 221 | **/*.DesktopClient/ModelManifest.xml 222 | **/*.Server/GeneratedArtifacts 223 | **/*.Server/ModelManifest.xml 224 | _Pvt_Extensions 225 | 226 | # Paket dependency manager 227 | .paket/paket.exe 228 | 229 | # FAKE - F# Make 230 | .fake/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cheatengine-threadstack-finder 2 | A simple program that list all thread's base address based on process's name. 3 | **ONLY WORK FOR** 4 | - **32 bit process** 5 | - **A process launched by you so that it has enough access rights** 6 | IF YOU SEE BASE ADDRESS AS 0x00000000, it propably is a 64-bit process. 7 | 8 | Pass command process id (PID) in decimal as a command line argument. 9 | The program will output all thread's base addresses. 10 | 11 | Make sure that a target process is opened before passing an argument. 12 | 13 | # Example 14 | Input 15 | ``` 16 | threadstack.exe 6540 17 | ``` 18 | Output 19 | ``` 20 | PID 6540 (0x198c) 21 | Grabbing handle 22 | Success 23 | PID: 6540 Thread ID: 0x1990 24 | PID: 6540 Thread ID: 0x1b1c 25 | PID: 6540 Thread ID: 0x1bbc 26 | TID: 0x1990 = THREADSTACK 0 BASE ADDRESS: 0xbcff8c 27 | TID: 0x1b1c = THREADSTACK 1 BASE ADDRESS: 0x4d8ff8c 28 | TID: 0x1bbc = THREADSTACK 2 BASE ADDRESS: 0x518ff8c 29 | ``` 30 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | // list all PIDs and TIDs 9 | #include 10 | #include 11 | 12 | #include "ntinfo.h" 13 | 14 | std::vector threadList(DWORD pid); 15 | DWORD GetThreadStartAddress(HANDLE processHandle, HANDLE hThread); 16 | 17 | int main(int argc, char** argv) { 18 | std::string pid = argv[1]; 19 | DWORD dwProcID; 20 | 21 | std::stringstream stringstream(pid); 22 | stringstream >> std::dec >> dwProcID; 23 | 24 | if (!dwProcID) { 25 | std::cerr << pid << " is not a valid process id (PID)" << std::endl; 26 | return EXIT_FAILURE; 27 | } 28 | 29 | HANDLE hProcHandle = NULL; 30 | 31 | printf("PID %d (0x%x)\n", dwProcID, dwProcID); 32 | std::cout << "Grabbing handle" << std::endl; 33 | hProcHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcID); 34 | 35 | if (hProcHandle == INVALID_HANDLE_VALUE || hProcHandle == NULL) { 36 | std::cerr << "Failed to open process -- invalid handle" << std::endl; 37 | std::cerr << "Error code: " << GetLastError() << std::endl; 38 | return EXIT_FAILURE; 39 | } 40 | else { 41 | std::cout << "Success" << std::endl; 42 | } 43 | 44 | std::vector threadId = threadList(dwProcID); 45 | int stackNum = 0; 46 | for (auto it = threadId.begin(); it != threadId.end(); ++it) { 47 | HANDLE threadHandle = OpenThread(THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, FALSE, *it); 48 | DWORD threadStartAddress = GetThreadStartAddress(hProcHandle, threadHandle); 49 | printf("TID: 0x%04x = THREADSTACK%2d BASE ADDRESS: 0x%04x\n", *it, stackNum, threadStartAddress); 50 | stackNum++; 51 | } 52 | 53 | return EXIT_SUCCESS; 54 | } 55 | 56 | std::vector threadList(DWORD pid) { 57 | /* solution from http://stackoverflow.com/questions/1206878/enumerating-threads-in-windows */ 58 | std::vector vect = std::vector(); 59 | HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); 60 | if (h == INVALID_HANDLE_VALUE) 61 | return vect; 62 | 63 | THREADENTRY32 te; 64 | te.dwSize = sizeof(te); 65 | if (Thread32First(h, &te)) { 66 | do { 67 | if (te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + 68 | sizeof(te.th32OwnerProcessID)) { 69 | 70 | 71 | if (te.th32OwnerProcessID == pid) { 72 | printf("PID: %04d Thread ID: 0x%04x\n", te.th32OwnerProcessID, te.th32ThreadID); 73 | vect.push_back(te.th32ThreadID); 74 | } 75 | 76 | } 77 | te.dwSize = sizeof(te); 78 | } while (Thread32Next(h, &te)); 79 | } 80 | 81 | return vect; 82 | } 83 | 84 | DWORD GetThreadStartAddress(HANDLE processHandle, HANDLE hThread) { 85 | /* rewritten from https://github.com/cheat-engine/cheat-engine/blob/master/Cheat%20Engine/CEFuncProc.pas#L3080 */ 86 | DWORD used = 0, ret = 0; 87 | DWORD stacktop = 0, result = 0; 88 | 89 | MODULEINFO mi; 90 | 91 | GetModuleInformation(processHandle, GetModuleHandle("kernel32.dll"), &mi, sizeof(mi)); 92 | stacktop = (DWORD)GetThreadStackTopAddress_x86(processHandle, hThread); 93 | 94 | /* The stub below has the same result as calling GetThreadStackTopAddress_x86() 95 | change line 54 in ntinfo.cpp to return tbi.TebBaseAddress 96 | Then use this stub 97 | */ 98 | //LPCVOID tebBaseAddress = GetThreadStackTopAddress_x86(processHandle, hThread); 99 | //if (tebBaseAddress) 100 | // ReadProcessMemory(processHandle, (LPCVOID)((DWORD)tebBaseAddress + 4), &stacktop, 4, NULL); 101 | 102 | /* rewritten from 32 bit stub (line3141) 103 | Result: fail -- can't get GetThreadContext() 104 | */ 105 | //CONTEXT context; 106 | //LDT_ENTRY ldtentry; 107 | //GetModuleInformation(processHandle, LoadLibrary("kernel32.dll"), &mi, sizeof(mi)); 108 | // 109 | //if (GetThreadContext(processHandle, &context)) { 110 | // 111 | // if (GetThreadSelectorEntry(hThread, context.SegFs, &ldtentry)) { 112 | // ReadProcessMemory(processHandle, 113 | // (LPCVOID)( (DWORD*)(ldtentry.BaseLow + ldtentry.HighWord.Bytes.BaseMid << ldtentry.HighWord.Bytes.BaseHi << 24) + 4), 114 | // &stacktop, 115 | // 4, 116 | // NULL); 117 | // } 118 | //} 119 | 120 | CloseHandle(hThread); 121 | 122 | if (stacktop) { 123 | //find the stack entry pointing to the function that calls "ExitXXXXXThread" 124 | //Fun thing to note: It's the first entry that points to a address in kernel32 125 | 126 | DWORD* buf32 = new DWORD[4096]; 127 | 128 | if (ReadProcessMemory(processHandle, (LPCVOID)(stacktop - 4096), buf32, 4096, NULL)) { 129 | for (int i = 4096 / 4 - 1; i >= 0; --i) { 130 | if (buf32[i] >= (DWORD)mi.lpBaseOfDll && buf32[i] <= (DWORD)mi.lpBaseOfDll + mi.SizeOfImage) { 131 | result = stacktop - 4096 + i * 4; 132 | break; 133 | } 134 | 135 | } 136 | } 137 | 138 | delete buf32; 139 | } 140 | 141 | return result; 142 | } 143 | -------------------------------------------------------------------------------- /ntinfo.cpp: -------------------------------------------------------------------------------- 1 | #include "ntinfo.h" 2 | 3 | typedef struct _CLIENT_ID 4 | { 5 | PVOID UniqueProcess; 6 | PVOID UniqueThread; 7 | } CLIENT_ID, *PCLIENT_ID; 8 | 9 | typedef struct _THREAD_BASIC_INFORMATION 10 | { 11 | NTSTATUS ExitStatus; 12 | PVOID TebBaseAddress; 13 | CLIENT_ID ClientId; 14 | KAFFINITY AffinityMask; 15 | KPRIORITY Priority; 16 | KPRIORITY BasePriority; 17 | } THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION; 18 | 19 | enum THREADINFOCLASS 20 | { 21 | ThreadBasicInformation, 22 | }; 23 | 24 | void* GetThreadStackTopAddress_x86(HANDLE hProcess, HANDLE hThread) { 25 | 26 | LPSTR moduleName = "ntdll.dll"; 27 | 28 | bool loadedManually = false; 29 | HMODULE module = GetModuleHandle(moduleName); 30 | 31 | if (!module) 32 | { 33 | module = LoadLibrary(moduleName); 34 | loadedManually = true; 35 | } 36 | 37 | NTSTATUS(__stdcall *NtQueryInformationThread)(HANDLE ThreadHandle, THREADINFOCLASS ThreadInformationClass, PVOID ThreadInformation, ULONG ThreadInformationLength, PULONG ReturnLength); 38 | NtQueryInformationThread = reinterpret_cast(GetProcAddress(module, "NtQueryInformationThread")); 39 | 40 | if (NtQueryInformationThread) 41 | { 42 | NT_TIB tib = { 0 }; 43 | THREAD_BASIC_INFORMATION tbi = { 0 }; 44 | 45 | NTSTATUS status = NtQueryInformationThread(hThread, ThreadBasicInformation, &tbi, sizeof(tbi), nullptr); 46 | if (status >= 0) 47 | { 48 | ReadProcessMemory(hProcess, tbi.TebBaseAddress, &tib, sizeof(tbi), nullptr); 49 | 50 | if (loadedManually) 51 | { 52 | FreeLibrary(module); 53 | } 54 | return tib.StackBase; 55 | } 56 | } 57 | 58 | 59 | if (loadedManually) 60 | { 61 | FreeLibrary(module); 62 | } 63 | 64 | return nullptr; 65 | } 66 | 67 | -------------------------------------------------------------------------------- /ntinfo.h: -------------------------------------------------------------------------------- 1 | /* solution from http://stackoverflow.com/questions/32297431/getting-the-tib-teb-of-a-thread-by-its-thread-handle-2015 */ 2 | 3 | #ifndef NTINFO_H 4 | #define NTINFO_H 5 | 6 | #include 7 | #include 8 | 9 | typedef LONG NTSTATUS; 10 | typedef DWORD KPRIORITY; 11 | typedef WORD UWORD; 12 | 13 | void* GetThreadStackTopAddress_x86(HANDLE hProcess, HANDLE hThread); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /threadstack.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | 26 | 27 | Header Files 28 | 29 | 30 | -------------------------------------------------------------------------------- /threadstack.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "threadstack", "threadstack.vcxproj", "{2F2D8AAC-46AB-4E80-AD8A-8EDDBA44E5EA}" 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 | {2F2D8AAC-46AB-4E80-AD8A-8EDDBA44E5EA}.Debug|x64.ActiveCfg = Debug|x64 17 | {2F2D8AAC-46AB-4E80-AD8A-8EDDBA44E5EA}.Debug|x64.Build.0 = Debug|x64 18 | {2F2D8AAC-46AB-4E80-AD8A-8EDDBA44E5EA}.Debug|x86.ActiveCfg = Debug|Win32 19 | {2F2D8AAC-46AB-4E80-AD8A-8EDDBA44E5EA}.Debug|x86.Build.0 = Debug|Win32 20 | {2F2D8AAC-46AB-4E80-AD8A-8EDDBA44E5EA}.Release|x64.ActiveCfg = Release|x64 21 | {2F2D8AAC-46AB-4E80-AD8A-8EDDBA44E5EA}.Release|x64.Build.0 = Release|x64 22 | {2F2D8AAC-46AB-4E80-AD8A-8EDDBA44E5EA}.Release|x86.ActiveCfg = Release|Win32 23 | {2F2D8AAC-46AB-4E80-AD8A-8EDDBA44E5EA}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /threadstack.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 | {2F2D8AAC-46AB-4E80-AD8A-8EDDBA44E5EA} 23 | Win32Proj 24 | threadstack 25 | 8.1 26 | threadstack 27 | 28 | 29 | 30 | Application 31 | true 32 | v140 33 | MultiByte 34 | 35 | 36 | Application 37 | false 38 | v140 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v140 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v140 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 | true 96 | 97 | 98 | 99 | 100 | 101 | 102 | Level3 103 | Disabled 104 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 105 | 106 | 107 | Console 108 | true 109 | 110 | 111 | 112 | 113 | Level3 114 | 115 | 116 | MaxSpeed 117 | true 118 | true 119 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 120 | 121 | 122 | Console 123 | true 124 | true 125 | true 126 | 127 | 128 | 129 | 130 | Level3 131 | 132 | 133 | MaxSpeed 134 | true 135 | true 136 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 137 | 138 | 139 | Console 140 | true 141 | true 142 | true 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | --------------------------------------------------------------------------------