├── .gitattributes ├── .gitignore ├── README.md ├── psr1.sln └── psr1 ├── ReadMe.txt ├── Tracer.cpp ├── Tracer.h ├── capstone.dll ├── capstone.lib ├── configuration.h ├── debugger.cpp ├── debugger.h ├── psr1.cpp ├── psr1.vcxproj ├── psr1.vcxproj.filters ├── stdafx.cpp ├── stdafx.h └── targetver.h /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | *.VC.opendb 5 | *.VC.db 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | build/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUNIT 36 | *.VisualState.xml 37 | TestResult.xml 38 | 39 | # Build Results of an ATL Project 40 | [Dd]ebugPS/ 41 | [Rr]eleasePS/ 42 | dlldata.c 43 | 44 | # DNX 45 | project.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | 84 | # Visual Studio profiler 85 | *.psess 86 | *.vsp 87 | *.vspx 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 | 113 | # MightyMoose 114 | *.mm.* 115 | AutoTest.Net/ 116 | 117 | # Web workbench (sass) 118 | .sass-cache/ 119 | 120 | # Installshield output folder 121 | [Ee]xpress/ 122 | 123 | # DocProject is a documentation generator add-in 124 | DocProject/buildhelp/ 125 | DocProject/Help/*.HxT 126 | DocProject/Help/*.HxC 127 | DocProject/Help/*.hhc 128 | DocProject/Help/*.hhk 129 | DocProject/Help/*.hhp 130 | DocProject/Help/Html2 131 | DocProject/Help/html 132 | 133 | # Click-Once directory 134 | publish/ 135 | 136 | # Publish Web Output 137 | *.[Pp]ublish.xml 138 | *.azurePubxml 139 | ## TODO: Comment the next line if you want to checkin your 140 | ## web deploy settings but do note that will include unencrypted 141 | ## passwords 142 | #*.pubxml 143 | 144 | *.publishproj 145 | 146 | # NuGet Packages 147 | *.nupkg 148 | # The packages folder can be ignored because of Package Restore 149 | **/packages/* 150 | # except build/, which is used as an MSBuild target. 151 | !**/packages/build/ 152 | # Uncomment if necessary however generally it will be regenerated when needed 153 | #!**/packages/repositories.config 154 | 155 | # Windows Azure Build Output 156 | csx/ 157 | *.build.csdef 158 | 159 | # Windows Store app package directory 160 | AppPackages/ 161 | 162 | # Visual Studio cache files 163 | # files ending in .cache can be ignored 164 | *.[Cc]ache 165 | # but keep track of directories ending in .cache 166 | !*.[Cc]ache/ 167 | 168 | # Others 169 | ClientBin/ 170 | [Ss]tyle[Cc]op.* 171 | ~$* 172 | *~ 173 | *.dbmdl 174 | *.dbproj.schemaview 175 | *.pfx 176 | *.publishsettings 177 | node_modules/ 178 | orleans.codegen.cs 179 | 180 | # RIA/Silverlight projects 181 | Generated_Code/ 182 | 183 | # Backup & report files from converting an old project file 184 | # to a newer Visual Studio version. Backup files are not needed, 185 | # because we have git ;-) 186 | _UpgradeReport_Files/ 187 | Backup*/ 188 | UpgradeLog*.XML 189 | UpgradeLog*.htm 190 | 191 | # SQL Server files 192 | *.mdf 193 | *.ldf 194 | 195 | # Business Intelligence projects 196 | *.rdl.data 197 | *.bim.layout 198 | *.bim_*.settings 199 | 200 | # Microsoft Fakes 201 | FakesAssemblies/ 202 | 203 | # Node.js Tools for Visual Studio 204 | .ntvs_analysis.dat 205 | 206 | # Visual Studio 6 build log 207 | *.plg 208 | 209 | # Visual Studio 6 workspace options file 210 | *.opt 211 | 212 | # LightSwitch generated files 213 | GeneratedArtifacts/ 214 | _Pvt_Extensions/ 215 | ModelManifest.xml 216 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pointer-chain-reversal 2 | 3 | Designed to help a reverse engineer easily see how a Windows C++ application is accessing a particular data member or object. 4 | 5 | Given the memory address of a data member or object, this tool will set a memory breakpoint at that address and then produce traces of the instructions executed prior to reading from or writing to that address. Some processing will be performed on the trace to highlight relevant instructions, make the output more readable, identify vtable pointers, etc. 6 | -------------------------------------------------------------------------------- /psr1.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "psr1", "psr1\psr1.vcxproj", "{460539C4-0B29-4416-BB1D-6E1B622FA59F}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{314CAAF9-EE83-4B78-AA46-4B24330A92F4}" 9 | ProjectSection(SolutionItems) = preProject 10 | Performance1.psess = Performance1.psess 11 | Performance2.psess = Performance2.psess 12 | EndProjectSection 13 | EndProject 14 | Global 15 | GlobalSection(Performance) = preSolution 16 | HasPerformanceSessions = true 17 | EndGlobalSection 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|x64 = Debug|x64 20 | Debug|x86 = Debug|x86 21 | Release|x64 = Release|x64 22 | Release|x86 = Release|x86 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {460539C4-0B29-4416-BB1D-6E1B622FA59F}.Debug|x64.ActiveCfg = Debug|x64 26 | {460539C4-0B29-4416-BB1D-6E1B622FA59F}.Debug|x64.Build.0 = Debug|x64 27 | {460539C4-0B29-4416-BB1D-6E1B622FA59F}.Debug|x86.ActiveCfg = Debug|Win32 28 | {460539C4-0B29-4416-BB1D-6E1B622FA59F}.Debug|x86.Build.0 = Debug|Win32 29 | {460539C4-0B29-4416-BB1D-6E1B622FA59F}.Release|x64.ActiveCfg = Release|x64 30 | {460539C4-0B29-4416-BB1D-6E1B622FA59F}.Release|x64.Build.0 = Release|x64 31 | {460539C4-0B29-4416-BB1D-6E1B622FA59F}.Release|x86.ActiveCfg = Release|Win32 32 | {460539C4-0B29-4416-BB1D-6E1B622FA59F}.Release|x86.Build.0 = Release|Win32 33 | EndGlobalSection 34 | GlobalSection(SolutionProperties) = preSolution 35 | HideSolutionNode = FALSE 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /psr1/ReadMe.txt: -------------------------------------------------------------------------------- 1 | ======================================================================== 2 | CONSOLE APPLICATION : psr1 Project Overview 3 | ======================================================================== 4 | 5 | AppWizard has created this psr1 application for you. 6 | 7 | This file contains a summary of what you will find in each of the files that 8 | make up your psr1 application. 9 | 10 | 11 | psr1.vcxproj 12 | This is the main project file for VC++ projects generated using an Application Wizard. 13 | It contains information about the version of Visual C++ that generated the file, and 14 | information about the platforms, configurations, and project features selected with the 15 | Application Wizard. 16 | 17 | psr1.vcxproj.filters 18 | This is the filters file for VC++ projects generated using an Application Wizard. 19 | It contains information about the association between the files in your project 20 | and the filters. This association is used in the IDE to show grouping of files with 21 | similar extensions under a specific node (for e.g. ".cpp" files are associated with the 22 | "Source Files" filter). 23 | 24 | psr1.cpp 25 | This is the main application source file. 26 | 27 | ///////////////////////////////////////////////////////////////////////////// 28 | Other standard files: 29 | 30 | StdAfx.h, StdAfx.cpp 31 | These files are used to build a precompiled header (PCH) file 32 | named psr1.pch and a precompiled types file named StdAfx.obj. 33 | 34 | ///////////////////////////////////////////////////////////////////////////// 35 | Other notes: 36 | 37 | AppWizard uses "TODO:" comments to indicate parts of the source code you 38 | should add to or customize. 39 | 40 | ///////////////////////////////////////////////////////////////////////////// 41 | -------------------------------------------------------------------------------- /psr1/Tracer.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "Tracer.h" 3 | #include "debugger.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define MAX_INSN_LENGTH 16 11 | 12 | 13 | Tracer::Tracer(HANDLE target_handle) 14 | : cs_handle(NULL), target_handle(target_handle) 15 | { 16 | InitializeCapstone(); 17 | } 18 | 19 | int Tracer::InitializeCapstone() 20 | { 21 | if (cs_open(CS_ARCH_X86, CS_MODE_32, &cs_handle) != CS_ERR_OK) std::cout << "Error in cs_open()" << std::endl; 22 | cs_option(cs_handle, CS_OPT_DETAIL, CS_OPT_ON); 23 | 24 | return 1; 25 | } 26 | 27 | 28 | Tracer::~Tracer() 29 | {} 30 | 31 | int Tracer::SaveInstruction(uint8_t* instruction_buffer, DWORD thread_id, const CONTEXT& thread_context) 32 | { 33 | DWORD previous_eip = 0, 34 | current_eip = thread_context.Eip; 35 | 36 | if (!all_threads_saved_instructions[thread_id].empty()) 37 | { 38 | // get<0> since eip (i.e., address) for an instruction is first element of instrution tuple 39 | previous_eip = std::get<0>(all_threads_saved_instructions[thread_id].back()); 40 | } 41 | 42 | // check that prev eip and current eip dont match, or else mem bp triggering instructions will get added twice 43 | if (previous_eip != current_eip) 44 | { 45 | //std::tuple, std::map> instruction; 46 | std::map modifications; 47 | 48 | //if (thread_context.Eip != all_threads_saved_contexts[thread_id].Eip) modifications["Eip"] = thread_context.Eip; 49 | if (thread_context.Eax != all_threads_saved_contexts[thread_id].Eax) modifications["Eax"] = thread_context.Eax; 50 | if (thread_context.Ebx != all_threads_saved_contexts[thread_id].Ebx) modifications["Ebx"] = thread_context.Ebx; 51 | if (thread_context.Ecx != all_threads_saved_contexts[thread_id].Ecx) modifications["Ecx"] = thread_context.Ecx; 52 | if (thread_context.Edx != all_threads_saved_contexts[thread_id].Edx) modifications["Edx"] = thread_context.Edx; 53 | if (thread_context.Edi != all_threads_saved_contexts[thread_id].Edi) modifications["Edi"] = thread_context.Edi; 54 | if (thread_context.Esi != all_threads_saved_contexts[thread_id].Esi) modifications["Esi"] = thread_context.Esi; 55 | 56 | all_threads_saved_contexts[thread_id] = thread_context; 57 | 58 | std::array insn_buffer = { 0 }; 59 | 60 | for (unsigned int i = 0; i < MAX_INSN_LENGTH; i++) 61 | { 62 | insn_buffer[i] = instruction_buffer[i]; 63 | } 64 | 65 | auto instruction = std::make_tuple(thread_context.Eip, insn_buffer, modifications); 66 | 67 | if (all_threads_saved_instructions[thread_id].size() >= max_trace_length) 68 | { 69 | all_threads_saved_instructions[thread_id].erase(all_threads_saved_instructions[thread_id].begin()); 70 | } 71 | 72 | all_threads_saved_instructions[thread_id].push_back(instruction); 73 | } 74 | 75 | return 1; 76 | } 77 | 78 | cs_insn Tracer::GetCsInsnFromBytes(std::array insn_bytes, DWORD address) 79 | { 80 | uint8_t insn_buf[MAX_INSN_LENGTH] = { 0 }; 81 | size_t count = 0; 82 | cs_insn insn; 83 | cs_insn *insnp; 84 | 85 | for (size_t i = 0; i < MAX_INSN_LENGTH; i++) 86 | { 87 | insn_buf[i] = insn_bytes[i]; 88 | } 89 | 90 | count = cs_disasm(cs_handle, insn_buf, MAX_INSN_LENGTH, address, count, &insnp); 91 | insn = insnp[0]; 92 | 93 | return insn; 94 | } 95 | 96 | int Tracer::AnalyzeRunTrace(DWORD thread_id, EXCEPTION_RECORD exception_record) 97 | { 98 | std::string reg_name; 99 | bool found = false; 100 | cs_insn insn; 101 | DWORD address; 102 | run_trace_vec run_trace = all_threads_saved_instructions[thread_id]; 103 | 104 | std::array raw_insn = std::get<1>(run_trace.back()); 105 | address = std::get<0>(run_trace.back()); 106 | 107 | insn = GetCsInsnFromBytes(raw_insn, address); 108 | 109 | size_t trace_pos = run_trace.end() - run_trace.begin() - 1; 110 | size_t last_trace_pos = 0; 111 | 112 | if (exception_record.ExceptionInformation[0] == 0) // true when the memory access violation was a read 113 | { 114 | std::cout << "Memory access violation was a read, starting trace analysis" << std::endl; 115 | reg_name = GetRegisterReadFrom(thread_id, insn, trace_pos); 116 | } 117 | else 118 | { 119 | // reg_name = GetRegisterWrittenTo(thread_id, insn, trace_pos); 120 | // maybe give an option to the user to manually choose register written to 121 | std::cout << "Memory access violation was a write, skipping trace analysis" << std::endl << std::endl; 122 | return 0; 123 | } 124 | 125 | DWORD value = 0, vtable = 0; 126 | uint64_t last_insn_address = 0; 127 | std::list> relevant_instructions; 128 | 129 | while (true) 130 | { 131 | value = GetValueOfRegisterForInstruction(thread_id, reg_name, insn, found, trace_pos); 132 | if (!found) break; 133 | found = false; 134 | 135 | vtable = GetVTableIfThereIsOne(value); 136 | 137 | relevant_instructions.push_back(std::make_tuple(insn, value, vtable)); 138 | 139 | //trace_pos = FindEarliestOccurenceOfValueInTrace(thread_id, value); // returns instruction position in trace 140 | trace_pos = FindMostRecentOccurenceOfValueInTrace(thread_id, value, trace_pos); 141 | if (trace_pos == 0) break; 142 | 143 | raw_insn = std::get<1>(run_trace.at(trace_pos)); 144 | address = std::get<0>(run_trace.at(trace_pos)); 145 | 146 | insn = GetCsInsnFromBytes(raw_insn, address); 147 | 148 | if (trace_pos == last_trace_pos) break; 149 | last_trace_pos = trace_pos; 150 | 151 | reg_name = GetRegisterReadFrom(thread_id, insn, trace_pos); 152 | if (reg_name == "No registers read") 153 | { 154 | relevant_instructions.push_back(std::make_tuple(insn, value, vtable)); 155 | break; 156 | } 157 | 158 | if (GetAsyncKeyState(0x51)) break; 159 | } 160 | 161 | PrintRunTrace(relevant_instructions); 162 | 163 | // do this if not verbose 164 | ReduceRunTrace(relevant_instructions); 165 | 166 | PrintRunTrace(relevant_instructions); 167 | 168 | return 1; 169 | } 170 | 171 | int Tracer::ReduceRunTrace(std::list> &relevant_instructions) 172 | { 173 | DWORD value; 174 | 175 | if (relevant_instructions.size() <= 1) return 0; 176 | 177 | for (auto ins = relevant_instructions.begin(); ins != relevant_instructions.end(); ins++) 178 | { 179 | auto rel_insn = *ins; 180 | value = std::get<1>(rel_insn); 181 | 182 | auto last_occurence = ins; 183 | 184 | for (auto jns = ins; jns != relevant_instructions.end(); jns++) 185 | { 186 | auto rel_jnsn = *jns; 187 | DWORD current_value = std::get<1>(rel_jnsn); 188 | 189 | if (current_value == value) last_occurence = jns; 190 | } 191 | 192 | if (std::next(last_occurence, 1) == relevant_instructions.end()) continue; 193 | 194 | relevant_instructions.erase(std::next(ins, 1), std::next(last_occurence, 1)); 195 | } 196 | } 197 | 198 | int Tracer::PrintRunTrace(std::list> relevant_instructions) 199 | { 200 | std::string full_insn_string; 201 | std::string mnemonic; 202 | std::string op_str; 203 | uint64_t eip; 204 | DWORD value, vtable; 205 | 206 | std::cout << "-- Trace analysis completed --" << std::endl; 207 | std::cout << std::left << std::setfill(' ') << std::setw(12) << "EIP"; 208 | std::cout << std::setw(32) << "Instruction"; 209 | std::cout << std::setw(11) << "Value"; 210 | std::cout << std::setw(10) << "VTable" << std::endl; 211 | 212 | for (auto ins = relevant_instructions.rbegin(); ins != relevant_instructions.rend(); ins++) 213 | { 214 | auto rel_insn = *ins; 215 | eip = std::get<0>(rel_insn).address; 216 | mnemonic = std::get<0>(rel_insn).mnemonic; 217 | op_str = std::get<0>(rel_insn).op_str; 218 | value = std::get<1>(rel_insn); 219 | vtable = std::get<2>(rel_insn); 220 | 221 | full_insn_string = mnemonic + " " + op_str; 222 | 223 | std::cout << std::internal << "0x" << std::setfill('0') << std::setw(8) << eip << std::setfill(' '); 224 | std::cout << " " << std::left << std::setw(32) << full_insn_string; 225 | 226 | if (value != 0) 227 | { 228 | std::cout << std::internal << "0x" << std::setfill('0') << std::setw(8) << std::hex << value; 229 | } 230 | 231 | if (vtable != 0) 232 | { 233 | std::cout << " " << std::internal << "0x" << std::setfill('0') << std::setw(8) << std::hex << vtable; 234 | } 235 | 236 | std::cout << std::endl; 237 | } 238 | 239 | std::cout << "-------- End of trace --------" << std::endl << std::endl; 240 | 241 | return 1; 242 | } 243 | 244 | bool Tracer::IsStaticAddress(DWORD value) 245 | { 246 | if (value >= 0x400000 && value <= 0xA3D000) return true; 247 | 248 | return false; 249 | } 250 | 251 | DWORD Tracer::GetVTableIfThereIsOne(DWORD value) 252 | { 253 | unsigned int potential_vtable_ptr = 0, potential_vtable = 0; 254 | MEMORY_BASIC_INFORMATION mem_info1 = { 0 }, mem_info2 = { 0 }; 255 | 256 | // 4 is x86 specific 257 | if (!ReadProcessMemory(target_handle, (LPCVOID)value, &potential_vtable_ptr, 4, NULL)) return 0; 258 | 259 | if (!VirtualQueryEx(target_handle, (LPCVOID)potential_vtable_ptr, &mem_info1, sizeof(MEMORY_BASIC_INFORMATION))) return 0; 260 | 261 | if (mem_info1.Protect != PAGE_READONLY) return 0; 262 | 263 | if (!ReadProcessMemory(target_handle, (LPCVOID)potential_vtable_ptr, &potential_vtable, 4, NULL)) return 0; 264 | 265 | if (!VirtualQueryEx(target_handle, (LPCVOID)potential_vtable, &mem_info2, sizeof(MEMORY_BASIC_INFORMATION))) return 0; 266 | 267 | unsigned int code_protection = PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_READONLY; 268 | 269 | if (!(mem_info2.Protect & code_protection)) return 0; 270 | 271 | return potential_vtable_ptr; 272 | } 273 | 274 | std::string Tracer::GetRegisterReadFrom(DWORD thread_id, cs_insn insn, const size_t trace_pos) 275 | { 276 | std::string reg_name = "No registers read", temp_reg_name, op_str = insn.op_str; 277 | cs_regs regs_read, regs_write; 278 | uint8_t read_count, write_count; 279 | unsigned int reg_count = 0; 280 | 281 | std::vector my_regs_read; 282 | 283 | cs_regs_access(cs_handle, &insn, regs_read, &read_count, regs_write, &write_count); 284 | 285 | if (read_count > 0) 286 | { 287 | bool found = false; 288 | 289 | // figure out what to do about instructions like "mov eax, [ecx + edx * 4]" 290 | // probably want to guess that register with higher value contains useful address 291 | DWORD this_value = 0, last_value = 0; 292 | 293 | for (int i = 0; i < read_count; i++) 294 | { 295 | temp_reg_name = cs_reg_name(cs_handle, regs_read[i]); 296 | temp_reg_name[0] = toupper(temp_reg_name[0]); 297 | 298 | this_value = GetValueOfRegisterForInstruction(thread_id, temp_reg_name.c_str(), insn, found, trace_pos); 299 | 300 | if (this_value >= last_value) 301 | { 302 | reg_name = temp_reg_name; 303 | last_value = this_value; 304 | } 305 | } 306 | } 307 | 308 | reg_name[0] = toupper(reg_name[0]); 309 | 310 | return reg_name; 311 | } 312 | 313 | std::string Tracer::GetRegisterWrittenTo(DWORD thread_id, cs_insn insn, const size_t trace_pos) 314 | { 315 | std::string reg_name = "No registers written", temp_reg_name, op_str = insn.op_str; 316 | cs_regs regs_read, regs_write; 317 | uint8_t read_count, write_count; 318 | unsigned int reg_count = 0; 319 | 320 | std::vector my_regs_read; 321 | 322 | cs_regs_access(cs_handle, &insn, regs_read, &read_count, regs_write, &write_count); 323 | 324 | if (write_count > 0) 325 | { 326 | bool found = false; 327 | 328 | // figure out what to do about instructions like "mov eax, [ecx + edx * 4]" 329 | // probably want to guess that register with higher value contains useful address 330 | DWORD this_value = 0, last_value = 0; 331 | 332 | for (int i = 0; i < write_count; i++) 333 | { 334 | temp_reg_name = cs_reg_name(cs_handle, regs_write[i]); 335 | temp_reg_name[0] = toupper(temp_reg_name[0]); 336 | 337 | this_value = GetValueOfRegisterForInstruction(thread_id, temp_reg_name.c_str(), insn, found, trace_pos); 338 | 339 | if (this_value >= last_value) 340 | { 341 | reg_name = temp_reg_name; 342 | last_value = this_value; 343 | } 344 | } 345 | } 346 | 347 | reg_name[0] = toupper(reg_name[0]); 348 | 349 | return reg_name; 350 | } 351 | 352 | DWORD Tracer::GetValueOfRegisterForInstruction(DWORD thread_id, std::string reg_name, cs_insn insn, bool& found, const size_t start_trace_pos) 353 | { 354 | uint64_t address_of_instruction = insn.address; 355 | //auto run_trace = all_threads_saved_instructions[thread_id]; 356 | std::map modifications; 357 | run_trace_vec run_trace = all_threads_saved_instructions[thread_id]; 358 | 359 | //std::reverse_iterator::iterator> trace_it = run_trace.rend() - start_trace_pos; 360 | 361 | for (size_t trace_pos = start_trace_pos; trace_pos > 0; trace_pos--) 362 | { 363 | instruction_info insn_info = run_trace.at(trace_pos); 364 | modifications = std::get<2>(insn_info); 365 | 366 | auto modification = modifications.find(reg_name.c_str()); 367 | 368 | if (modification != modifications.end()) 369 | { 370 | found = true; 371 | return modification->second; 372 | } 373 | } 374 | 375 | found = false; 376 | return 0; 377 | } 378 | 379 | size_t Tracer::FindEarliestOccurenceOfValueInTrace(DWORD thread_id, DWORD value) 380 | { 381 | run_trace_vec run_trace = all_threads_saved_instructions[thread_id]; 382 | cs_insn *insnp; 383 | DWORD address; 384 | std::array raw_insn; 385 | size_t count = 0; 386 | std::map modifications; 387 | uint8_t raw_insn_buf[MAX_INSN_LENGTH] = { 0 }; 388 | size_t trace_pos = 0; 389 | 390 | for (size_t trace_pos = 0; trace_pos < run_trace.size(); trace_pos++) 391 | { 392 | instruction_info insn_info = run_trace.at(trace_pos); 393 | modifications = std::get<2>(insn_info); 394 | 395 | for (auto mod = modifications.begin(); mod != modifications.end(); mod++) 396 | { 397 | if (mod->second == value && trace_pos != 0) 398 | { 399 | // just decrement ins iterator and grab prev instruction 400 | size_t prev_trace_pos = trace_pos - 1; 401 | instruction_info prev_insn_info = run_trace.at(prev_trace_pos); 402 | /* 403 | address = std::get<0>(prev_insn_info); 404 | raw_insn = std::get<1>(prev_insn_info); 405 | 406 | for (size_t i = 0; i < MAX_INSN_LENGTH; i++) 407 | { 408 | raw_insn_buf[i] = raw_insn[i]; 409 | } 410 | 411 | count = cs_disasm(cs_handle, raw_insn_buf, MAX_INSN_LENGTH, address, count, &insnp); 412 | */ 413 | return prev_trace_pos; 414 | } 415 | } 416 | } 417 | 418 | // should do something about error condition 419 | return 0; 420 | } 421 | 422 | size_t Tracer::FindMostRecentOccurenceOfValueInTrace(DWORD thread_id, DWORD value, size_t start_trace_pos) 423 | { 424 | run_trace_vec run_trace = all_threads_saved_instructions[thread_id]; 425 | std::map modifications; 426 | DWORD address; 427 | std::array raw_insn; 428 | uint8_t raw_insn_buf[MAX_INSN_LENGTH] = { 0 }; 429 | size_t count = 0; 430 | cs_insn *insnp; 431 | cs_insn insn; 432 | cs_regs regs_read, regs_write; 433 | uint8_t read_count, write_count; 434 | std::string reg_read; 435 | bool reg_read_is_stack_reg = true; 436 | size_t rel_trace_pos = 0; 437 | 438 | for (size_t trace_pos = start_trace_pos; trace_pos > 0; trace_pos--) 439 | { 440 | instruction_info insn_info = run_trace.at(trace_pos); 441 | modifications = std::get<2>(insn_info); 442 | 443 | for (auto mod = modifications.begin(); mod != modifications.end(); mod++) 444 | { 445 | if (mod->second == value && trace_pos != 0) 446 | { 447 | // just decrement ins iterator and grab prev instruction 448 | size_t prev_trace_pos = trace_pos - 1; 449 | instruction_info prev_insn_info = run_trace.at(prev_trace_pos); 450 | 451 | address = std::get<0>(prev_insn_info); 452 | raw_insn = std::get<1>(prev_insn_info); 453 | 454 | for (size_t i = 0; i < MAX_INSN_LENGTH; i++) raw_insn_buf[i] = raw_insn[i]; 455 | 456 | count = cs_disasm(cs_handle, raw_insn_buf, MAX_INSN_LENGTH, address, count, &insnp); 457 | insn = insnp[0]; 458 | 459 | if (count > 0) 460 | { 461 | reg_read = GetRegisterReadFrom(thread_id, insn, prev_trace_pos); 462 | 463 | if (reg_read == "Esp" || reg_read == "Ebp") 464 | { 465 | reg_read_is_stack_reg = true; 466 | } 467 | else 468 | { 469 | reg_read_is_stack_reg = false; 470 | rel_trace_pos = prev_trace_pos; 471 | } 472 | } 473 | 474 | break; // maybe break only if reg read is good 475 | } 476 | } 477 | 478 | if (!reg_read_is_stack_reg) return rel_trace_pos; 479 | } 480 | 481 | return rel_trace_pos; 482 | } -------------------------------------------------------------------------------- /psr1/Tracer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "capstone.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "Tracer.h" 15 | #include 16 | 17 | #define MAX_INSN_LENGTH 16 18 | 19 | class Tracer 20 | { 21 | public: 22 | Tracer(HANDLE target_handle); 23 | ~Tracer(); 24 | 25 | int SaveInstruction(uint8_t* instruction_buffer, DWORD thread_id, const CONTEXT& thread_context); 26 | //int SaveInstructionInfo(uint8_t* instruction_buffer, size_t max_insn_size, DWORD thread_id, const CONTEXT& thread_context); 27 | int AnalyzeRunTrace(DWORD thread_id, EXCEPTION_RECORD exception_record); 28 | 29 | csh cs_handle; 30 | 31 | private: 32 | int InitializeCapstone(); // maybe just put this in constructor? also set options for details 33 | 34 | DWORD GetValueOfRegisterForInstruction(DWORD thread_id, std::string reg_name, cs_insn insn, bool& found, const size_t start_trace_pos); 35 | size_t FindEarliestOccurenceOfValueInTrace(DWORD thread_id, DWORD value); 36 | size_t FindMostRecentOccurenceOfValueInTrace(DWORD thread_id, DWORD value, size_t start_trace_pos); 37 | std::string GetRegisterReadFrom(DWORD thread_id, cs_insn insn, const size_t trace_pos); 38 | std::string GetRegisterWrittenTo(DWORD thread_id, cs_insn insn, const size_t trace_pos); 39 | bool IsStaticAddress(DWORD value); 40 | DWORD GetVTableIfThereIsOne(DWORD value); 41 | cs_insn GetCsInsnFromBytes(std::array insn_bytes, DWORD address); 42 | int PrintRunTrace(std::list> relevant_instructions); 43 | int ReduceRunTrace(std::list> &relevant_instructions); 44 | 45 | typedef DWORD instruction_address; 46 | typedef std::array instruction_bytes; 47 | typedef std::map register_modifications; 48 | 49 | typedef std::tuple instruction_info; 50 | typedef std::deque run_trace_vec; 51 | 52 | std::map all_threads_saved_instructions; 53 | std::map all_threads_saved_contexts; 54 | //const size_t x86_MAX_INSTRUCTION_LENGTH = 15; 55 | const size_t max_trace_length = 25000; 56 | const size_t max_instruction_length = 15; 57 | HANDLE target_handle; 58 | }; 59 | 60 | -------------------------------------------------------------------------------- /psr1/capstone.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ncollisson/pointer-sequence-reversal/5b84a02870b2bd08190d457fd9795aed3ef382bf/psr1/capstone.dll -------------------------------------------------------------------------------- /psr1/capstone.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ncollisson/pointer-sequence-reversal/5b84a02870b2bd08190d457fd9795aed3ef382bf/psr1/capstone.lib -------------------------------------------------------------------------------- /psr1/configuration.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | -------------------------------------------------------------------------------- /psr1/debugger.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "debugger.h" 3 | #include 4 | #include 5 | #include 6 | #include "capstone.h" 7 | //#include 8 | #include 9 | //#include "CsCapstoneHelper.hh" 10 | //#include "Disasm.hpp" 11 | //#include "CsIns.hpp" 12 | //#include "X86Disasm.hh" 13 | #include "Tracer.h" 14 | int (WINAPIV * __vsnprintf)(char *, size_t, const char*, va_list) = _vsnprintf; 15 | 16 | 17 | Debugger::Debugger() 18 | { 19 | // probably bad 20 | // Tracer: cs_open(CS_ARCH_X86, CS_MODE_32, &cs_handle); 21 | 22 | // also, where to initialize tracer? 23 | } 24 | 25 | Debugger::~Debugger() 26 | { 27 | } 28 | 29 | int Debugger::SetTargetPID(DWORD target_pid) 30 | { 31 | this->target_pid = target_pid; 32 | return 1; 33 | } 34 | 35 | int Debugger::SetTargetAddress(LPVOID target_address) 36 | { 37 | this->target_address = target_address; 38 | return 1; 39 | } 40 | 41 | int Debugger::Attach() 42 | { 43 | if (!DebugActiveProcess(target_pid)) 44 | { 45 | std::cout << "Error in DebugActiveProcess(): " << GetLastError() << std::endl; 46 | return 0; 47 | } 48 | 49 | if (!DebugSetProcessKillOnExit(FALSE)) 50 | { 51 | std::cout << "Could not DebugSetProcessKillOnExit(FALSE)" << std::endl; 52 | } 53 | 54 | std::cout << "Now debugging the target process" << std::endl; 55 | 56 | return 1; 57 | } 58 | 59 | int Debugger::SetMemoryBreakpoint(LPVOID target_address = NULL) 60 | { 61 | DWORD old_protect; 62 | MEMORY_BASIC_INFORMATION page_info; 63 | PMEMORY_BASIC_INFORMATION ppage_info = &page_info; 64 | size_t breakpoint_size = 1; 65 | 66 | if (target_handle == NULL) 67 | { 68 | target_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, target_pid); 69 | 70 | if (!target_handle) 71 | { 72 | std::cout << "Error in OpenProcess(): " << GetLastError() << std::endl; 73 | return 0; 74 | } 75 | 76 | std::cout << "Obtained handle to target process" << std::endl; 77 | } 78 | 79 | if (tracer == NULL) 80 | { 81 | tracer = std::make_unique(target_handle); 82 | } 83 | 84 | if (target_address == NULL) 85 | { 86 | target_address = this->target_address; 87 | 88 | if (target_address == NULL) 89 | { 90 | std::cout << "Won't attempt to set breakpoint at NULL address" << std::endl; 91 | return 0; 92 | } 93 | } 94 | 95 | if (!VirtualQueryEx(target_handle, target_address, ppage_info, sizeof(MEMORY_BASIC_INFORMATION))) 96 | { 97 | std::cout << "Error in VirtualQueryEx(): " << GetLastError() << std::endl; 98 | return 0; 99 | } 100 | 101 | orig_protect = ppage_info->Protect; 102 | 103 | if (!VirtualProtectEx(target_handle, target_address, breakpoint_size, orig_protect | PAGE_GUARD, &old_protect)) 104 | { 105 | std::cout << "Error in VirtualProtectEx(): " << GetLastError() << std::endl; 106 | return 0; 107 | } 108 | 109 | //std::cout << "Set memory breakpoint, waiting for debug event" << std::endl; 110 | 111 | return 1; 112 | } 113 | 114 | int Debugger::RemoveMemoryBreakpoint() 115 | { 116 | DWORD old_protect; 117 | MEMORY_BASIC_INFORMATION page_info; 118 | PMEMORY_BASIC_INFORMATION ppage_info = &page_info; 119 | size_t breakpoint_size = 1; 120 | 121 | if (!VirtualProtectEx(target_handle, target_address, breakpoint_size, orig_protect, &old_protect)) 122 | { 123 | std::cout << "Error in VirtualProtectEx(): " << GetLastError() << std::endl; 124 | return 0; 125 | } 126 | 127 | return 1; 128 | } 129 | 130 | int Debugger::WaitForMemoryBreakpoint() 131 | { 132 | EXCEPTION_RECORD exception_record; 133 | DEBUG_EVENT debug_event; 134 | LPDEBUG_EVENT lpdebug_event = &debug_event; 135 | DWORD offending_thread_ID; 136 | HANDLE new_thread_handle; 137 | DWORD new_thread_id; 138 | CONTEXT thread_context; 139 | BOOL wait_to_set_mem_bp = FALSE; 140 | const size_t MAX_INSTRUCTION_LENGTH = 15; 141 | uint8_t instruction_buffer[2 * MAX_INSTRUCTION_LENGTH] = {0}; 142 | SIZE_T num_bytes_read; 143 | size_t cs_count; 144 | cs_insn *insn; 145 | 146 | // maybe up here set trap flag 147 | 148 | // 0x51 == Q key 149 | while (!GetAsyncKeyState(0x51)) 150 | { 151 | if (!WaitForDebugEvent(lpdebug_event, INFINITE)) 152 | { 153 | std::cout << "Error in WaitForDebugEvent(): " << GetLastError() << std::endl; 154 | return 0; 155 | } 156 | 157 | exception_record = lpdebug_event->u.Exception.ExceptionRecord; 158 | 159 | switch (lpdebug_event->dwDebugEventCode) 160 | { 161 | case EXCEPTION_DEBUG_EVENT: 162 | 163 | offending_thread_ID = lpdebug_event->dwThreadId; 164 | 165 | switch (exception_record.ExceptionCode) 166 | { 167 | case STATUS_GUARD_PAGE_VIOLATION: 168 | if (IsMemoryBreakpointHit(debug_event)) 169 | { 170 | GetCurrentThreadContext(offending_thread_ID, thread_context); 171 | 172 | // profile this and other rpms, how bad are they? 173 | // probably doesnt matter too much this doesnt happen very often 174 | ReadProcessMemory(target_handle, LPCVOID (thread_context.Eip), instruction_buffer, sizeof(instruction_buffer), &num_bytes_read); 175 | 176 | cs_count = cs_disasm(tracer->cs_handle, instruction_buffer, sizeof(instruction_buffer), thread_context.Eip, 0, &insn); 177 | 178 | // do we need to do the first or second insn from buffer? 179 | // if first, just get rid of 2x max size 180 | cs_insn offending_instruction = insn[1]; 181 | 182 | // do insn_buffer = offending_instruction.byte buffer 183 | // then saveinstruction(insn_buffer) 184 | uint8_t* insn_buffer = insn->bytes; 185 | tracer->SaveInstruction(insn_buffer, offending_thread_ID, thread_context); 186 | 187 | //tracer->SaveInstructionInfo(instruction_buffer, max_insn_size, offending_thread_ID, thread_context); 188 | tracer->AnalyzeRunTrace(offending_thread_ID, exception_record); 189 | } 190 | 191 | SetTrapFlag(offending_thread_ID); 192 | ContinueDebugEvent(lpdebug_event->dwProcessId, lpdebug_event->dwThreadId, DBG_CONTINUE); 193 | 194 | // wait a little bit before setting the mem bp again so the target program can get 195 | // past the instruction that triggered the guard page violation. otherwise the target 196 | // program gets stuck on the same instruction over and over. should play around with 197 | // the amount of sleep time for optimal performance 198 | Sleep(200); 199 | SetMemoryBreakpoint(target_address); 200 | 201 | continue; 202 | 203 | case EXCEPTION_SINGLE_STEP: 204 | 205 | GetCurrentThreadContext(offending_thread_ID, thread_context); 206 | 207 | if (!ReadProcessMemory(target_handle, LPCVOID(thread_context.Eip), instruction_buffer, sizeof(instruction_buffer), &num_bytes_read)) 208 | { 209 | std::cout << "Error in ReadProcessMemory(): " << GetLastError() << std::endl; 210 | return 0; 211 | } 212 | 213 | tracer->SaveInstruction(instruction_buffer, offending_thread_ID, thread_context); 214 | 215 | SetTrapFlag(offending_thread_ID); 216 | 217 | break; 218 | 219 | default: 220 | //std::cout << "EXCEPTION_DEBUG_EVENT other than STATUS_GUARD_PAGE_VIOLATION or single step" << std::endl; 221 | //std::cout << "debug event code: " << exception_record.ExceptionCode << std::endl; 222 | break; 223 | } 224 | break; 225 | 226 | case CREATE_THREAD_DEBUG_EVENT: 227 | //std::cout << "create thread debug event" << std::endl; 228 | /* 229 | new_thread_handle = lpdebug_event->u.CreateProcessInfo.hThread; 230 | new_thread_id = GetThreadId(new_thread_handle); // GetThreadId is fucking up, get error code, find out why 231 | thread_handles.insert_or_assign(new_thread_id, new_thread_handle); 232 | SetTrapFlag(new_thread_id); 233 | */ 234 | 235 | break; 236 | 237 | default: 238 | //std::cout << "DEBUG_EVENT other than EXCEPTION_DEBUG_EVENT" << std::endl; 239 | //std::cout << "debug event code: " << lpdebug_event->dwDebugEventCode << std::endl; 240 | break; 241 | } 242 | 243 | ContinueDebugEvent(lpdebug_event->dwProcessId, lpdebug_event->dwThreadId, DBG_CONTINUE); 244 | } 245 | 246 | CleanUpAndExit(); 247 | } 248 | 249 | int Debugger::CleanUpAndExit() 250 | { 251 | RemoveMemoryBreakpoint(); 252 | 253 | DEBUG_EVENT debug_event; 254 | LPDEBUG_EVENT lpdebug_event = &debug_event; 255 | 256 | while (TRUE) 257 | { 258 | if (!WaitForDebugEvent(lpdebug_event, 2000)) break; 259 | ContinueDebugEvent(lpdebug_event->dwProcessId, lpdebug_event->dwThreadId, DBG_CONTINUE); 260 | } 261 | 262 | return 1; 263 | } 264 | 265 | int Debugger::IsMemoryBreakpointHit(const DEBUG_EVENT& debug_event) 266 | { 267 | LPVOID access_address; 268 | EXCEPTION_RECORD exception_record = debug_event.u.Exception.ExceptionRecord; 269 | unsigned int i, num = exception_record.NumberParameters; 270 | BOOL mem_breakpoint_hit = FALSE; 271 | 272 | //std::cout << "STATUS_GUARD_PAGE_VIOLATION: Page guard hit" << std::endl; 273 | 274 | for (i = 0; i < num; i++) 275 | { 276 | //std::cout << "ExceptionInformation[" << i << "]: " << std::hex << exception_record.ExceptionInformation[i] << std::endl; 277 | } 278 | 279 | // ExceptionInformation structure undefined for STATUS_GUARD_PAGE_VIOLATION 280 | // Consider using PAGE_NOACCESS instead of PAGE_GUARD since 281 | // ExceptionInformation for EXCEPTION_ACCESS_VIOLATION is defined. 282 | 283 | access_address = (num > 0 ? (LPVOID) exception_record.ExceptionInformation[num - 1] : NULL); 284 | 285 | if (access_address == target_address) 286 | { 287 | std::cout << "Memory breakpoint hit" << std::endl; 288 | mem_breakpoint_hit = TRUE; 289 | } 290 | 291 | return mem_breakpoint_hit; 292 | } 293 | 294 | int Debugger::SetSoftBreakpoint(LPVOID target_address) 295 | { 296 | char orig_instruction_byte; 297 | LPVOID lporig_instruction_byte = &orig_instruction_byte; 298 | char int3 = '\xCC'; 299 | LPCVOID lpcint3 = &int3; 300 | 301 | if (!ReadProcessMemory(target_handle, target_address, lporig_instruction_byte, 1, NULL)) 302 | { 303 | std::cout << "Error in ReadProcessMemory(): " << GetLastError() << std::endl; 304 | return 0; 305 | } 306 | 307 | soft_breakpoint_list[target_address] = orig_instruction_byte; 308 | 309 | if (!WriteProcessMemory(target_handle, target_address, lpcint3, 1, NULL)) 310 | { 311 | std::cout << "Error in WriteProcessMemory(): " << GetLastError() << std::endl; 312 | return 0; 313 | } 314 | 315 | return 1; 316 | } 317 | 318 | LPVOID Debugger::GetInstructionPointer(const DEBUG_EVENT& debug_event) 319 | { 320 | CONTEXT thread_context; 321 | LPCONTEXT lpthread_context = &thread_context; 322 | LPVOID instruction_pointer;; 323 | 324 | HANDLE thread_handle = OpenThread(THREAD_ALL_ACCESS, FALSE, debug_event.dwThreadId); 325 | 326 | if (!thread_handle) 327 | { 328 | std::cout << "Error in OpenThread(): " << GetLastError() << std::endl; 329 | return 0; 330 | } 331 | 332 | thread_context.ContextFlags = CONTEXT_ALL; 333 | 334 | if (!GetThreadContext(thread_handle, lpthread_context)) 335 | { 336 | std::cout << "Error in GetThreadContext(): " << GetLastError() << std::endl; 337 | return 0; 338 | } 339 | 340 | // x86 specific 341 | instruction_pointer = (LPVOID) lpthread_context->Eip; 342 | 343 | return instruction_pointer; 344 | } 345 | 346 | int Debugger::SetTrapFlag(DWORD thread_id) 347 | { 348 | HANDLE thread_handle; 349 | CONTEXT thread_context; 350 | 351 | if (thread_handles.find(thread_id) != thread_handles.end()) 352 | { 353 | thread_handle = thread_handles[thread_id]; 354 | } 355 | else 356 | { 357 | thread_handle = OpenThread(THREAD_ALL_ACCESS, FALSE, thread_id); 358 | 359 | if (!thread_handle) 360 | { 361 | std::cout << "Error in OpenThread(): " << GetLastError() << std::endl; 362 | return 0; 363 | } 364 | 365 | thread_handles.insert_or_assign(thread_id, thread_handle); 366 | } 367 | 368 | thread_context.ContextFlags = CONTEXT_CONTROL; 369 | 370 | if (!GetThreadContext(thread_handle, &thread_context)) 371 | { 372 | std::cout << "Error in GetThreadContext(): " << GetLastError() << std::endl; 373 | return 0; 374 | } 375 | 376 | // x86 specific 377 | thread_context.EFlags |= 0x100; 378 | 379 | if (!SetThreadContext(thread_handle, &thread_context)) 380 | { 381 | std::cout << "Error in SetThreadContext(): " << GetLastError() << std::endl; 382 | } 383 | 384 | return 1; 385 | } 386 | 387 | int Debugger::StartRecordingRegisterModifications() 388 | { 389 | // get handle for each thread 390 | // set single step breakpoints on each one 391 | 392 | ListProcessThreads(target_pid); 393 | 394 | for (auto & thread_ID : target_thread_IDs) 395 | { 396 | std::cout << "setting trap flag on thread id: " << thread_ID << std::endl; 397 | if (!SetTrapFlag(thread_ID)) std::cout << "error setting trap flag: " << GetLastError() << std::endl; 398 | } 399 | 400 | return 1; 401 | } 402 | 403 | int Debugger::GetCurrentThreadContext(DWORD thread_id, CONTEXT &thread_context) 404 | { 405 | // close all these handles 406 | HANDLE thread_handle = OpenThread(THREAD_ALL_ACCESS, FALSE, thread_id); 407 | 408 | if (!thread_handle) 409 | { 410 | std::cout << "Error in OpenThread(): " << GetLastError() << std::endl; 411 | return 0; 412 | } 413 | 414 | thread_context.ContextFlags = CONTEXT_ALL; 415 | 416 | if (!GetThreadContext(thread_handle, &thread_context)) 417 | { 418 | std::cout << "Error in GetThreadContext(): " << GetLastError() << std::endl; 419 | return 0; 420 | } 421 | 422 | return 1; 423 | } 424 | 425 | /* 426 | int Debugger::SaveRegisterChanges(DWORD thread_id, const CONTEXT &thread_context) 427 | { 428 | std::map modifications; 429 | 430 | if (thread_context.Eip != saved_thread_context.Eip) modifications["Eip"] = thread_context.Eip; 431 | if (thread_context.Eax != saved_thread_context.Eax) modifications["Eax"] = thread_context.Eax; 432 | if (thread_context.Ebx != saved_thread_context.Ebx) modifications["Ebx"] = thread_context.Ebx; 433 | if (thread_context.Ecx != saved_thread_context.Ecx) modifications["Ecx"] = thread_context.Ecx; 434 | if (thread_context.Edx != saved_thread_context.Edx) modifications["Edx"] = thread_context.Edx; 435 | if (thread_context.Edi != saved_thread_context.Edi) modifications["Edi"] = thread_context.Edi; 436 | if (thread_context.Esi != saved_thread_context.Esi) modifications["Esi"] = thread_context.Esi; 437 | 438 | if (modifications.size() > 1) 439 | { 440 | const unsigned int MAX_RECORD_LENGTH = 50; 441 | if (all_threads_register_changes[thread_id].size() > MAX_RECORD_LENGTH) 442 | { 443 | all_threads_register_changes[thread_id].erase(all_threads_register_changes[thread_id].begin()); 444 | } 445 | 446 | all_threads_register_changes[thread_id].push_back(modifications); 447 | } 448 | 449 | return 1; 450 | } 451 | */ 452 | /* 453 | int Debugger::PrintRegisterChanges(DWORD thread_id) 454 | { 455 | DWORD Eip_value; 456 | 457 | std::cout << "trace for thread: " << thread_id << std::endl; 458 | 459 | for (auto &instruction_map : all_threads_register_changes[thread_id]) 460 | { 461 | Eip_value = instruction_map["Eip"]; 462 | std::cout << "Eip: " << Eip_value << ", "; 463 | 464 | for (auto ®ister_modification : instruction_map) 465 | { 466 | if (register_modification.first != "Eip") 467 | { 468 | std::cout << register_modification.first << ": " << std::hex << register_modification.second << ", "; 469 | } 470 | } 471 | 472 | std::cout << std::endl; 473 | } 474 | return 1; 475 | } 476 | */ 477 | /* 478 | int Debugger::PrintRunTrace(DWORD thread_id) 479 | { 480 | DWORD Eip_value; 481 | cs_insn insn; 482 | std::map modifications; 483 | 484 | std::cout << "trace for thread: " << thread_id << std::endl; 485 | 486 | for (auto &instruction_tuple : all_threads_saved_instructions[thread_id]) 487 | { 488 | std::tie(Eip_value, insn, modifications) = instruction_tuple; 489 | 490 | std::cout << "0x" << std::hex << Eip_value << "\t" << insn.mnemonic << " " << insn.op_str << "\tmods: "; 491 | 492 | for (auto &modification : modifications) 493 | { 494 | std::cout << modification.first << " " << std::hex << modification.second << ", "; 495 | } 496 | 497 | std::cout << "\n\n"; 498 | } 499 | 500 | return 1; 501 | } 502 | */ 503 | 504 | // taken from msdn 505 | BOOL Debugger::ListProcessThreads(DWORD dwOwnerPID) 506 | { 507 | HANDLE hThreadSnap = INVALID_HANDLE_VALUE; 508 | THREADENTRY32 te32; 509 | 510 | // Take a snapshot of all running threads 511 | hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); 512 | if (hThreadSnap == INVALID_HANDLE_VALUE) 513 | return(FALSE); 514 | 515 | // Fill in the size of the structure before using it. 516 | te32.dwSize = sizeof(THREADENTRY32); 517 | 518 | // Retrieve information about the first thread, 519 | // and exit if unsuccessful 520 | if (!Thread32First(hThreadSnap, &te32)) 521 | { 522 | std::cout << TEXT("Thread32First") << std::endl; // Show cause of failure 523 | CloseHandle(hThreadSnap); // Must clean up the snapshot object! 524 | return(FALSE); 525 | } 526 | 527 | do 528 | { 529 | if (te32.th32OwnerProcessID == dwOwnerPID) 530 | { 531 | // store thread ID 532 | target_thread_IDs.push_back(te32.th32ThreadID); 533 | } 534 | } while (Thread32Next(hThreadSnap, &te32)); 535 | 536 | // Don't forget to clean up the snapshot object. 537 | CloseHandle(hThreadSnap); 538 | return(TRUE); 539 | } -------------------------------------------------------------------------------- /psr1/debugger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "capstone.h" 8 | #include "Tracer.h" 9 | 10 | 11 | class Debugger 12 | { 13 | public: 14 | Debugger(); 15 | ~Debugger(); 16 | 17 | int SetTargetPID(DWORD target_pid); 18 | int SetTargetAddress(LPVOID target_address); 19 | int Attach(); 20 | int SetMemoryBreakpoint(LPVOID target_address); 21 | int RemoveMemoryBreakpoint(); 22 | int WaitForMemoryBreakpoint(); 23 | int SetSoftBreakpoint(LPVOID target_address); 24 | int StartRecordingRegisterModifications(); 25 | int CleanUpAndExit(); 26 | //int PrintRegisterChanges(DWORD thread_id); 27 | 28 | private: 29 | DWORD target_pid; 30 | LPVOID target_address; 31 | HANDLE target_handle = NULL; 32 | std::map soft_breakpoint_list; 33 | LPVOID instruction_address; 34 | //CONTEXT saved_thread_context; 35 | std::vector target_thread_IDs; 36 | 37 | std::map thread_handles; 38 | 39 | // Tracer: csh cs_handle = NULL; 40 | 41 | // Not needed: std::map>> all_threads_register_changes; 42 | 43 | // Tracer: std::map all_threads_saved_contexts; 44 | 45 | // map 46 | // vector 47 | // tuple 48 | // map 49 | // Tracer: std::map>>> all_threads_saved_instructions; 50 | 51 | size_t MAX_TRACE_LENGTH = 50; 52 | size_t max_insn_size = 15; 53 | DWORD orig_protect; 54 | 55 | //std::map> 56 | 57 | BOOL IsMemoryBreakpointHit(const DEBUG_EVENT& debug_event); 58 | LPVOID GetInstructionPointer(const DEBUG_EVENT& debug_event); 59 | int SetTrapFlag(DWORD thread_id); 60 | int GetCurrentThreadContext(DWORD thread_id, CONTEXT &thread_context); 61 | //int SaveRegisterChanges(DWORD thread_id, const CONTEXT &thread_context); 62 | BOOL ListProcessThreads(DWORD dwOwnerPID); 63 | //int SaveInstructionInfo(DWORD thread_id, const CONTEXT& thread_context); 64 | //int PrintRunTrace(DWORD thread_id); 65 | //int AnalyzeRunTrace(DWORD offending_thread_ID, CONTEXT thread_context, uint16_t register_ID, uint8_t read_count); 66 | //int PrintAnalysis(DWORD offending_thread_ID, unsigned int analysis_ID); 67 | 68 | std::unique_ptr tracer = NULL; 69 | }; 70 | 71 | -------------------------------------------------------------------------------- /psr1/psr1.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include 3 | #include 4 | #include "debugger.h" 5 | #include 6 | 7 | 8 | int main() 9 | { 10 | DWORD target_pid; 11 | LPVOID target_address; 12 | 13 | std::cout << "Enter PID of target process: " << std::endl; 14 | std::cin >> target_pid; 15 | 16 | std::cout << "Enter address of interest in hexadecimal (without 0x): " << std::endl; 17 | std::cin >> std::hex >> target_address; 18 | 19 | std::unique_ptr debugger(new Debugger); 20 | debugger->SetTargetPID(target_pid); 21 | debugger->SetTargetAddress(target_address); 22 | 23 | debugger->Attach(); 24 | 25 | // get each thread debug message 26 | // make a new record trace object for each thread 27 | // set trap flag on each thread 28 | 29 | // then set memory breakpoint 30 | 31 | // then wait for it to be hit 32 | // then pull run trace for that thread 33 | 34 | //while (TRUE) 35 | //{ 36 | /* 37 | lets try dumber approach of setting mem bp and recording 38 | just do a sanity check that the last instruction recorded actually 39 | hits the mem bp 40 | 41 | 42 | */ 43 | debugger->StartRecordingRegisterModifications(); 44 | debugger->SetMemoryBreakpoint(target_address); 45 | std::cout << "waiting for memory access..." << std::endl; 46 | if (!debugger->WaitForMemoryBreakpoint()) 47 | { 48 | std::cout << "Error in WaitForMemoryBreakpoint()" << std::endl; 49 | } 50 | 51 | //debugger->PrintRegisterChanges(); 52 | //debugger->AnalyzeRunTrace(); 53 | //} 54 | 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /psr1/psr1.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 | {460539C4-0B29-4416-BB1D-6E1B622FA59F} 23 | Win32Proj 24 | psr1 25 | 8.1 26 | 27 | 28 | 29 | Application 30 | true 31 | v140 32 | Unicode 33 | 34 | 35 | Application 36 | false 37 | v140 38 | true 39 | Unicode 40 | 41 | 42 | Application 43 | true 44 | v140 45 | Unicode 46 | 47 | 48 | Application 49 | false 50 | v140 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | true 74 | C:\Users\nick\Desktop\capstone-3.0.4-win32;$(LibraryPath) 75 | 76 | 77 | true 78 | 79 | 80 | false 81 | C:\Users\nick\Desktop\capstone-3.0.4-win32;$(LibraryPath) 82 | 83 | 84 | false 85 | 86 | 87 | 88 | Use 89 | Level3 90 | Disabled 91 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 92 | true 93 | C:\Users\nick\Documents\capstone-next\include\capstone;%(AdditionalIncludeDirectories) 94 | 95 | 96 | Console 97 | true 98 | C:\Users\nick\Desktop\capstone-next\msvc\Debug\capstone.lib;C:\Users\nick\Desktop\capstone-3.0.4-win32\capstone.lib;legacy_stdio_definitions.lib;%(AdditionalDependencies) 99 | 100 | 101 | 102 | 103 | Use 104 | Level3 105 | Disabled 106 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 107 | true 108 | 109 | 110 | Console 111 | true 112 | 113 | 114 | 115 | 116 | Level3 117 | Use 118 | MaxSpeed 119 | true 120 | true 121 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 122 | true 123 | C:\Users\nick\Documents\capstone-next\include\capstone;%(AdditionalIncludeDirectories) 124 | 125 | 126 | Console 127 | true 128 | true 129 | true 130 | C:\Users\nick\Desktop\capstone-next\msvc\Debug\capstone.lib;C:\Users\nick\Desktop\capstone-3.0.4-win32\capstone.lib;legacy_stdio_definitions.lib;%(AdditionalDependencies) 131 | 132 | 133 | 134 | 135 | Level3 136 | Use 137 | MaxSpeed 138 | true 139 | true 140 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 141 | true 142 | 143 | 144 | Console 145 | true 146 | true 147 | true 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | Create 164 | Create 165 | Create 166 | Create 167 | 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /psr1/psr1.vcxproj.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 | 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | 35 | 36 | Source Files 37 | 38 | 39 | Source Files 40 | 41 | 42 | Source Files 43 | 44 | 45 | Source Files 46 | 47 | 48 | -------------------------------------------------------------------------------- /psr1/stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // psr1.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | 7 | // TODO: reference any additional headers you need in STDAFX.H 8 | // and not in this file 9 | -------------------------------------------------------------------------------- /psr1/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #include "targetver.h" 9 | 10 | #include 11 | #include 12 | 13 | 14 | 15 | // TODO: reference additional headers your program requires here 16 | -------------------------------------------------------------------------------- /psr1/targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 7 | 8 | #include 9 | --------------------------------------------------------------------------------