├── unwinder ├── .gitignore ├── .cargo │ └── config.toml ├── build.rs ├── Cargo.toml └── src │ ├── gateway.asm │ └── lib.rs ├── .github └── FUNDING.yml ├── images ├── no_main.png ├── spoofed.png ├── main_kept.jpg ├── not_spoofed.png ├── stack_replacement.png └── start_stack_replacement.png ├── LICENSE ├── .gitignore └── README.md /unwinder/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [Kudaes] -------------------------------------------------------------------------------- /images/no_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kudaes/Unwinder/HEAD/images/no_main.png -------------------------------------------------------------------------------- /images/spoofed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kudaes/Unwinder/HEAD/images/spoofed.png -------------------------------------------------------------------------------- /images/main_kept.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kudaes/Unwinder/HEAD/images/main_kept.jpg -------------------------------------------------------------------------------- /images/not_spoofed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kudaes/Unwinder/HEAD/images/not_spoofed.png -------------------------------------------------------------------------------- /images/stack_replacement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kudaes/Unwinder/HEAD/images/stack_replacement.png -------------------------------------------------------------------------------- /images/start_stack_replacement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kudaes/Unwinder/HEAD/images/start_stack_replacement.png -------------------------------------------------------------------------------- /unwinder/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "x86_64-pc-windows-msvc" 3 | rustflags = ["--remap-path-prefix", "C:\\Users\\=", "--remap-path-prefix", "unwinder="] -------------------------------------------------------------------------------- /unwinder/build.rs: -------------------------------------------------------------------------------- 1 | fn main() 2 | { 3 | // Use the `cc` crate to build a C file and statically link it. 4 | cc::Build::new() 5 | .file("src/gateway.asm") 6 | .compile("gateway"); 7 | } -------------------------------------------------------------------------------- /unwinder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "unwinder" 3 | version = "0.1.1" 4 | edition = "2021" 5 | 6 | [profile.dev] 7 | debug-assertions = false 8 | 9 | [profile.release] 10 | debug-assertions = false # required to avoid misaligned pointer dereference panics 11 | strip = true 12 | 13 | [features] 14 | Experimental = ["lazy_static"] 15 | 16 | [dependencies] 17 | dinvoke_rs = "=0.2.0" 18 | bitreader = "0.3.8" 19 | nanorand = "0.7.0" 20 | winapi = "0.3.9" 21 | litcrypt2 = "=0.1.2" 22 | lazy_static = {version = "1.4.0", optional = true} 23 | 24 | [build-dependencies] 25 | cc = "1.0.83" 26 | 27 | [dependencies.windows] 28 | version = "0.51" 29 | features = [ 30 | "Win32_Foundation", 31 | "Win32_Security", 32 | "Win32_System", 33 | "Win32_System_IO", 34 | "Win32_System_Kernel", 35 | "Win32_System_Diagnostics_Debug", 36 | "Win32_System_Diagnostics_ToolHelp", 37 | "Win32_System_WindowsProgramming", 38 | "Wdk_Foundation", 39 | "Win32_Storage_FileSystem", 40 | "Win32_System_Memory", 41 | "Win32_System_SystemInformation" 42 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Kurosh Dabbagh Escalante 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 | -------------------------------------------------------------------------------- /.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 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | 84 | # Visual Studio profiler 85 | *.psess 86 | *.vsp 87 | *.vspx 88 | *.sap 89 | *.vs 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | # TODO: Comment the next line if you want to checkin your web deploy settings 143 | # but database connection strings (with potential passwords) will be unencrypted 144 | *.pubxml 145 | *.publishproj 146 | 147 | # NuGet Packages 148 | *.nupkg 149 | # The packages folder can be ignored because of Package Restore 150 | **/packages/* 151 | # except build/, which is used as an MSBuild target. 152 | !**/packages/build/ 153 | # Uncomment if necessary however generally it will be regenerated when needed 154 | #!**/packages/repositories.config 155 | 156 | # Microsoft Azure Build Output 157 | csx/ 158 | *.build.csdef 159 | 160 | # Microsoft Azure Emulator 161 | ecf/ 162 | rcf/ 163 | 164 | # Microsoft Azure ApplicationInsights config file 165 | ApplicationInsights.config 166 | 167 | # Windows Store app package directory 168 | AppPackages/ 169 | BundleArtifacts/ 170 | 171 | # Visual Studio cache files 172 | # files ending in .cache can be ignored 173 | *.[Cc]ache 174 | # but keep track of directories ending in .cache 175 | !*.[Cc]ache/ 176 | 177 | # Others 178 | ClientBin/ 179 | ~$* 180 | *~ 181 | *.dbmdl 182 | *.dbproj.schemaview 183 | *.pfx 184 | *.publishsettings 185 | node_modules/ 186 | orleans.codegen.cs 187 | 188 | # RIA/Silverlight projects 189 | Generated_Code/ 190 | 191 | # Backup & report files from converting an old project file 192 | # to a newer Visual Studio version. Backup files are not needed, 193 | # because we have git ;-) 194 | _UpgradeReport_Files/ 195 | Backup*/ 196 | UpgradeLog*.XML 197 | UpgradeLog*.htm 198 | 199 | # SQL Server files 200 | *.mdf 201 | *.ldf 202 | 203 | # Business Intelligence projects 204 | *.rdl.data 205 | *.bim.layout 206 | *.bim_*.settings 207 | 208 | # Microsoft Fakes 209 | FakesAssemblies/ 210 | 211 | # GhostDoc plugin setting file 212 | *.GhostDoc.xml 213 | 214 | # Node.js Tools for Visual Studio 215 | .ntvs_analysis.dat 216 | 217 | # Visual Studio 6 build log 218 | *.plg 219 | 220 | # Visual Studio 6 workspace options file 221 | *.opt 222 | 223 | # Visual Studio LightSwitch build output 224 | **/*.HTMLClient/GeneratedArtifacts 225 | **/*.DesktopClient/GeneratedArtifacts 226 | **/*.DesktopClient/ModelManifest.xml 227 | **/*.Server/GeneratedArtifacts 228 | **/*.Server/ModelManifest.xml 229 | _Pvt_Extensions 230 | 231 | # Paket dependency manager 232 | .paket/paket.exe 233 | 234 | # FAKE - F# Make 235 | .fake/ 236 | 237 | # Generated by Cargo 238 | # will have compiled files and executables 239 | debug/ 240 | target/ 241 | 242 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 243 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 244 | Cargo.lock 245 | 246 | # These are backup files generated by rustfmt 247 | **/*.rs.bk 248 | 249 | # MSVC Windows builds of rustc generate these, which store debugging information 250 | *.pdb -------------------------------------------------------------------------------- /unwinder/src/gateway.asm: -------------------------------------------------------------------------------- 1 | ; Great portions of the asm code below has been obtained from https://github.com/klezVirus. 2 | ; All credits to @KlezVirus @trickster012 @waldoirc and @namazso for developing the original PoC of the SilentMoonWalk technique. 3 | .data 4 | 5 | INFO_STRUCT STRUCT 6 | RtlAddr DQ 1 7 | RtlSize DQ 1 8 | BaseAddr DQ 1 9 | BaseSize DQ 1 10 | CurrentSize DQ 1 11 | TotalSize DQ 1 12 | INFO_STRUCT ENDS 13 | 14 | SPOOFER STRUCT 15 | 16 | GodGadget DQ 1 17 | RtlUnwindAddress DQ 1 18 | RtlUnwindTarget DQ 1 19 | Stub DQ 1 20 | FirstFrameFunctionPointer DQ 1 21 | SecondFrameFunctionPointer DQ 1 22 | JmpRbxGadget DQ 1 23 | AddRspXGadget DQ 1 24 | 25 | FirstFrameSize DQ 1 26 | SecondFrameSize DQ 1 27 | JmpRbxGadgetFrameSize DQ 1 28 | AddRspXGadgetFrameSize DQ 1 29 | 30 | StackOffsetWhereRbpIsPushed DQ 1 31 | 32 | SpoofFunctionPointer DQ 1 33 | ReturnAddress DQ 1 34 | 35 | Nargs DQ 1 36 | Arg01 DQ 1 37 | Arg02 DQ 1 38 | Arg03 DQ 1 39 | Arg04 DQ 1 40 | Arg05 DQ 1 41 | Arg06 DQ 1 42 | Arg07 DQ 1 43 | Arg08 DQ 1 44 | Arg09 DQ 1 45 | Arg10 DQ 1 46 | Arg11 DQ 1 47 | 48 | Sys DD 0 49 | SysId DD 0 50 | 51 | SPOOFER ENDS 52 | 53 | .code 54 | 55 | get_current_rsp proc 56 | mov rax, rsp 57 | add rax, 8 58 | ret 59 | get_current_rsp endp 60 | 61 | get_current_function_address proc 62 | mov rax, [rsp] 63 | ret 64 | get_current_function_address endp 65 | 66 | start_replacement proc 67 | mov rax, [rsp] 68 | mov r11, rsp 69 | 70 | add r11, 8 ; we discard current return address to get the original rsp value 71 | push rsp 72 | push rbp 73 | push r12 ; save nonvolatile registers 74 | push r15 75 | 76 | sub rsp, [rcx].INFO_STRUCT.RtlSize 77 | push [rcx].INFO_STRUCT.RtlAddr 78 | sub rsp, [rcx].INFO_STRUCT.BaseSize 79 | push [rcx].INFO_STRUCT.BaseAddr 80 | sub rsp,[rcx].INFO_STRUCT.CurrentSize 81 | 82 | mov r15, rbp 83 | sub r15, r11 84 | mov r12, rsp 85 | add r15, r12 86 | mov rbp, r15 87 | 88 | prepare_loop: 89 | mov r9, 0 ; offset 90 | start_loop_1: 91 | mov r8, r11 ; original rsp 92 | mov r12, rsp ; current rsp 93 | add r8, r9 94 | add r12, r9 95 | mov r10, [r8] 96 | mov [r12], r10 97 | add r9, 8 98 | cmp r9, [rcx].INFO_STRUCT.CurrentSize 99 | je end_loop_1 100 | jmp start_loop_1 101 | 102 | end_loop_1: 103 | jmp qword ptr rax 104 | start_replacement endp 105 | 106 | end_replacement proc 107 | pop r14 108 | mov r11, rsp ; original rsp 109 | mov r8, [rcx].INFO_STRUCT.TotalSize 110 | add rsp, r8 111 | pop r15 ; restore nonvolatile registers 112 | pop r12 113 | pop rbp 114 | pop rsp 115 | pop r9 ; old return address 116 | 117 | mov r9, 0 ; offset 118 | start_loop_2: 119 | mov r8, r11 ; original rsp 120 | mov rdx, rsp ; current rsp 121 | add r8, r9 122 | add rdx, r9 123 | mov r10, [r8] 124 | mov [rdx], r10 125 | add r9, 8 126 | cmp r9, [rcx].INFO_STRUCT.CurrentSize 127 | je end_loop_2 128 | jmp start_loop_2 129 | 130 | end_loop_2: 131 | jmp qword ptr r14 132 | end_replacement endp 133 | 134 | spoof_call2 proc 135 | 136 | mov rax, [rsp] 137 | mov r10, [rcx].SPOOFER.ReturnAddress 138 | mov [rsp], r10 139 | mov [rsp+08h], rbp 140 | mov [rsp+10h], rbx 141 | mov [rsp+18h], rax 142 | mov rbp, rsp 143 | 144 | lea rax, restore2 145 | push rax 146 | 147 | lea rbx, [rsp] 148 | add rsp, 8 ; (mis)alignment 149 | 150 | sub rsp, [rcx].SPOOFER.JmpRbxGadgetFrameSize 151 | push [rcx].SPOOFER.JmpRbxGadget 152 | sub rsp, [rcx].SPOOFER.AddRspXGadgetFrameSize 153 | push [rcx].SPOOFER.AddRspXGadget 154 | 155 | mov r11, [rcx].SPOOFER.SpoofFunctionPointer 156 | jmp parameter_handler 157 | spoof_call2 endp 158 | 159 | spoof_call proc 160 | 161 | mov [rsp+08h], rbp 162 | mov [rsp+10h], rbx 163 | mov rbp, rsp 164 | 165 | lea rax, restore 166 | push rax 167 | 168 | lea rbx, [rsp] 169 | 170 | push [rcx].SPOOFER.FirstFrameFunctionPointer 171 | 172 | mov rax, [rcx].SPOOFER.ReturnAddress 173 | sub rax, [rcx].SPOOFER.FirstFrameSize 174 | 175 | sub rsp, [rcx].SPOOFER.SecondFrameSize 176 | mov r10, [rcx].SPOOFER.StackOffsetWhereRbpIsPushed 177 | mov [rsp+r10], rax 178 | 179 | push [rcx].SPOOFER.SecondFrameFunctionPointer 180 | 181 | sub rsp, [rcx].SPOOFER.JmpRbxGadgetFrameSize 182 | push [rcx].SPOOFER.JmpRbxGadget 183 | sub rsp, [rcx].SPOOFER.AddRspXGadgetFrameSize 184 | push [rcx].SPOOFER.AddRspXGadget 185 | 186 | mov r11, [rcx].SPOOFER.SpoofFunctionPointer 187 | jmp parameter_handler 188 | spoof_call endp 189 | 190 | restore proc 191 | mov rsp, rbp 192 | mov rbp, [rsp+08h] 193 | mov rbx, [rsp+10h] 194 | ret 195 | restore endp 196 | 197 | restore2 proc 198 | mov rsp, rbp 199 | mov rbp, [rsp+18h] 200 | mov [rsp], rbp 201 | mov rbp, [rsp+08h] 202 | mov rbx, [rsp+10h] 203 | ret 204 | restore2 endp 205 | 206 | parameter_handler proc 207 | cmp [rcx].SPOOFER.Nargs, 11 208 | je handle_eleven 209 | cmp [rcx].SPOOFER.Nargs, 10 210 | je handle_ten 211 | cmp [rcx].SPOOFER.Nargs, 9 212 | je handle_nine 213 | cmp [rcx].SPOOFER.Nargs, 8 214 | je handle_eight 215 | cmp [rcx].SPOOFER.Nargs, 7 216 | je handle_seven 217 | cmp [rcx].SPOOFER.Nargs, 6 218 | je handle_six 219 | cmp [rcx].SPOOFER.Nargs, 5 220 | je handle_five 221 | cmp [rcx].SPOOFER.Nargs, 4 222 | je handle_four 223 | cmp [rcx].SPOOFER.Nargs, 3 224 | je handle_three 225 | cmp [rcx].SPOOFER.Nargs, 2 226 | je handle_two 227 | cmp [rcx].SPOOFER.Nargs, 1 228 | je handle_one 229 | cmp [rcx].SPOOFER.Nargs, 0 230 | je handle_none 231 | parameter_handler endp 232 | 233 | handle_eleven proc 234 | push r15 235 | mov r15, [rcx].SPOOFER.Arg11 236 | mov [rsp+60h], r15 237 | pop r15 238 | jmp handle_ten 239 | handle_eleven endp 240 | handle_ten proc 241 | push r15 242 | mov r15, [rcx].SPOOFER.Arg10 243 | mov [rsp+58h], r15 244 | pop r15 245 | jmp handle_nine 246 | handle_ten endp 247 | handle_nine proc 248 | push r15 249 | mov r15, [rcx].SPOOFER.Arg09 250 | mov [rsp+50h], r15 251 | pop r15 252 | jmp handle_eight 253 | handle_nine endp 254 | handle_eight proc 255 | push r15 256 | mov r15, [rcx].SPOOFER.Arg08 257 | mov [rsp+48h], r15 258 | pop r15 259 | jmp handle_seven 260 | handle_eight endp 261 | handle_seven proc 262 | push r15 263 | mov r15, [rcx].SPOOFER.Arg07 264 | mov [rsp+40h], r15 265 | pop r15 266 | jmp handle_six 267 | handle_seven endp 268 | handle_six proc 269 | push r15 270 | mov r15, [rcx].SPOOFER.Arg06 271 | mov [rsp+38h], r15 272 | pop r15 273 | jmp handle_five 274 | handle_six endp 275 | handle_five proc 276 | push r15 277 | mov r15, [rcx].SPOOFER.Arg05 278 | mov [rsp+30h], r15 279 | pop r15 280 | jmp handle_four 281 | handle_five endp 282 | handle_four proc 283 | mov r9, [rcx].SPOOFER.Arg04 284 | jmp handle_three 285 | handle_four endp 286 | handle_three proc 287 | mov r8, [rcx].SPOOFER.Arg03 288 | jmp handle_two 289 | handle_three endp 290 | handle_two proc 291 | mov rdx, [rcx].SPOOFER.Arg02 292 | jmp handle_one 293 | handle_two endp 294 | handle_one proc 295 | cmp [rcx].SPOOFER.Sys, 0 296 | jne execute_syscall 297 | mov rcx, [rcx].SPOOFER.Arg01 298 | jmp handle_none 299 | handle_one endp 300 | 301 | handle_none proc 302 | jmp execute 303 | handle_none endp 304 | 305 | execute proc 306 | jmp qword ptr r11 307 | execute endp 308 | 309 | execute_syscall proc 310 | mov r10, [rcx].SPOOFER.Arg01 311 | mov eax, [rcx].SPOOFER.SysId 312 | mov rcx, [rcx].SPOOFER.Arg01 313 | jmp qword ptr r11 314 | execute_syscall endp 315 | 316 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Content 2 | 3 | - [SilentMoonWalk](#SilentMoonWalk) 4 | - [Description](#Description) 5 | - [Credits](#Credits) 6 | - [Usage](#usage) 7 | - [call_function!() macro](#call_function-macro) 8 | - [indirect_syscall!() macro](#indirect_syscall-macro) 9 | - [Parameter passing](#Parameter-passing) 10 | - [Examples](#examples) 11 | - [Calling kernel32.dll!Sleep()](#Calling-Sleep) 12 | - [Calling kernel32.dll!OpenProcess()](#Calling-Openprocess) 13 | - [Calling NtDelayExecution() as indirect syscall](#Calling-NtDelayExecution-as-indirect-syscall) 14 | - [Concatenate macro calls](#Concatenate-macro-calls) 15 | - [Considerations](#Considerations) 16 | - [Initial frame](#Initial-frame) 17 | - [PoC](#PoC) 18 | - [Stack replacement](#Stack-replacement) 19 | - [Description](#Technique-description) 20 | - [Usage](#How-to-use-it) 21 | - [Practical example](#Example) 22 | - [Remarks](#Remarks) 23 | 24 | # SilentMoonWalk 25 | ## Description 26 | 27 | Unwinder provides a full weaponization of [SilentMoonWalk](https://github.com/klezVirus/SilentMoonwalk) technique, allowing to obtain complete and stable call stack spoofing in Rust. 28 | 29 | This technique comes with the following characteristics: 30 | * Support to run any arbitrary function with up to 11 parameters. 31 | * Support to run indirect syscalls (no additional heap allocations) with up to 11 parameters. 32 | * The crate allows to retrieve the value returned by the functions called through it. 33 | * The spoofing process can be concatenated any number of times without increasing the call stack size. 34 | * TLS is used to increase efficiency during the spoofing process. 35 | * [dinvoke_rs](https://crates.io/crates/dinvoke_rs) is used to make any Windows API call required by the crate. 36 | 37 | ## Credits 38 | kudos to the creators of the SilentMoonWalk technique: 39 | 40 | * [KlezVirus](https://twitter.com/KlezVirus) 41 | * [Waldo-IRC](https://twitter.com/waldoirc) 42 | * [Trickster0](https://twitter.com/trickster012) 43 | 44 | And of course a huge shoutout to [namazso](https://twitter.com/namazso) for the [Twitter thread](https://twitter.com/namazso/status/1442313752767045635?s=20&t=wxBHvf95-XtkPEevjcgbPg) that inspired this whole project. 45 | 46 | ## Usage 47 | 48 | Import this crate into your project by adding the following line to your `cargo.toml` and compile on `release` mode: 49 | 50 | ```rust 51 | [dependencies] 52 | unwinder = "=0.1.4" 53 | ``` 54 | 55 | The main functionality of this crate has been wrapped in two macros: 56 | * The `call_function!()` macro allows to run any arbitrary function with a clean call stack. 57 | * The `indirect_syscall!()` macro executes the specified (indirect) syscall with a clean call stack. 58 | 59 | To use any of these macros it is required to import `std::ffi::c_void` data type. 60 | 61 | Both macros return a `*mut c_void` that can be used to retrieve the value returned by the function executed. More detailed information in the examples section. 62 | 63 | ### call_function macro 64 | 65 | This macro is used to call any desired function with a clean call stack. 66 | The macro expects the following parameters: 67 | * The first parameter is the memory address to call after spoofing the call stack. This parameter should be passed as a `usize`, `isize` or a pointer. 68 | * The second parameter is a bool indicating whether or not keep the start function frame. If you are not sure about this, set it to false which always guarantees a good call stack. 69 | * The following parameters are those arguments to send to the function once the call stack has been spoofed. 70 | 71 | ### indirect_syscall macro 72 | 73 | This macro is used to perform any desired indirect syscall with a clean call stack. 74 | The macro expects the following parameters: 75 | * The first parameter is a string that contains the name of the NT function whose syscall you want to execute. 76 | * The second parameter is a bool indicating whether or not keep the start function frame. If you are not sure about this, set it to false which always guarantees a good call stack. 77 | * The following parameters are those arguments to send to the NT function. 78 | 79 | ### Parameter passing 80 | 81 | In order to pass arguments of different types to these two macros, the following considerations must be taken into account: 82 | * Any basic data type that can be converted to `usize` (u8-u64, i8-i64, bool, etc.) can be passed directly to the macros. 83 | * Structs and unions of size 8, 16, 32, or 64 bits are passed as if they were integers of the same size. 84 | * Structures and unions with a size larger than 64 bits must be passed as a pointer. 85 | * Strings (`&str` and `String`) must be passed as a pointer. 86 | * Null pointers (`ptr::null()`, `ptr::null_mut()`, etc. ) are passed as a 0 (no matter if it is `u8`, `u16`, `i32` or any other). 87 | * Floating-point and double-precision parameters are not currently supported. 88 | * Any other data type must be passed as a pointer. 89 | 90 | ## Examples 91 | ### Calling Sleep 92 | 93 | ```rust 94 | let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll"); 95 | let sleep = dinvoke_rs::dinvoke::get_function_address(k32, "Sleep"); // Memory address of kernel32.dll!Sleep() 96 | let miliseconds = 1000i32; 97 | unwinder::call_function!(sleep, false, miliseconds); 98 | ``` 99 | ### Calling OpenProcess 100 | 101 | ```rust 102 | let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll"); 103 | let open_process: isize = dinvoke_rs::dinvoke::get_function_address(k32, "Openprocess"); 104 | let desired_access: u32 = 0x1000; 105 | let inherit = 0i32; 106 | let pid = 20628i32; 107 | let handle = unwinder::call_function!(open_process, false, desired_access, inherit, pid); // returns *mut c_void 108 | let handle: HANDLE = std::mem::transmute(handle); 109 | println!("Handle id: {:x}", handle.0); 110 | ``` 111 | 112 | Notice that the macro returns a `*mut c_void` that can be directly converted to a `HANDLE` since both data types has the same size. This allows to access to the value returned by `OpenProcess`, which is the new handle to the target process. 113 | 114 | ### Calling NtDelayExecution as indirect syscall 115 | 116 | ```rust 117 | let large = 0x8000000000000000 as u64; // Sleep indefinitely 118 | let large: *mut i64 = std::mem::transmute(&large); 119 | let alertable = false; 120 | let ntstatus = unwinder::indirect_syscall!("NtDelayExecution", false, alertable, large); // returns *mut c_void 121 | println!("ntstatus: {:x}", ntstatus as i32); 122 | ``` 123 | Notice that the macro returns a `*mut c_void` that can be used to retrieve the `NTSTATUS` returned by `NtDelayExecution`. 124 | 125 | ### Concatenate macro calls 126 | 127 | The spoofing process can be concatenated any number of times without an abnormal call stack size increment. The execution flow will be preserved as well. The following code is an example of this: 128 | ```rust 129 | fn main() 130 | { 131 | function_a(); 132 | } 133 | 134 | fn function_a() 135 | { 136 | unsafe 137 | { 138 | let func_b = function_b as usize; 139 | call_function!(func_b, false); 140 | println!("function_a done."); 141 | } 142 | } 143 | 144 | fn function_b() 145 | { 146 | unsafe 147 | { 148 | let func_c = function_c as usize; 149 | call_function!(func_c, false); 150 | println!("function_b done.") 151 | } 152 | } 153 | 154 | fn function_c() 155 | { 156 | unsafe 157 | { 158 | let large = 0x0000000000000000 as u64; // Don't sleep so we return to function_b, allowing to check the execution flow preservation. 159 | let large: *mut i64 = std::mem::transmute(&large); 160 | let alertable = false; 161 | let ntstatus = unwinder::indirect_syscall!("NtDelayExecution", false, alertable, large); 162 | println!("ntstatus: {:x}", (ntstatus as usize) as i32); //NTSTATUS is a i32, although that second casting is not really required in this case. 163 | } 164 | } 165 | ``` 166 | 167 | ## Considerations 168 | ### Initial frame 169 | 170 | If you set the second parameter to true (both macros), the spoofing process will try to keep the thread start address' frame in the call stack to increase legitimacy. 171 | 172 | ![Call stack spoofed keeping the main module.](/images/main_kept.jpg "Call stack spoofed keeping the main module") 173 | 174 | 175 | Sometimes, the thread's start function does not perform a `call` to a subsequent function (e.g. a `jmp` instruction is executed instead), meaning there is not return address pushed to the stack. In that scenario (and also if you set that second parameter to false), the spoofed call stack will start at BaseThreadInitThunk's frame. 176 | 177 | ![Call stack spoofed without main module.](/images/no_main.png "Call stack spoofed without main module") 178 | 179 | 180 | ### PoC 181 | 182 | In order to test the implementation of the technique, [PE-sieve](https://github.com/hasherezade/pe-sieve) has been used with the flag [`/threads`](https://github.com/hasherezade/pe-sieve/wiki/4.9.-Scan-threads-callstack-(threads)). The results of the test shows how the inpection of the call stack does not reveal the pressence of the payload when this crate's functionalities are used. As it can be seen in the second image, the payload is detected when unwinder is not used. 183 | 184 | ![PE-sieve results when unwinder is used.](/images/spoofed.png "PE-sieve results when unwinder is used") 185 | ![PE-sieve results when unwinder is not used.](/images/not_spoofed.png "PE-sieve results when unwinder is used") 186 | 187 | # Stack replacement 188 | ## Technique description 189 | 190 | This is a call stack spoofing alternative to SilentMoonWalk that allows to keep a clean call stack during the execution of your program. The main idea behind this technique is that each called function inside your module takes care of the previously pushed return address, finding at runtime a legitimate function with the same frame size as that of the return address to be spoofed. Once a legitime function with the same frame size has been located, an offset within it is calculated and the final address is used to **replace** the last return address, hiding any anomalous entry in the call stack and keeping it unwindable. The original return address is stored by `unwinder` and it is moved back to the right position in the stack before a return instruction is executed, allowing to continue the normal flow of the program. 191 | 192 |

193 | Stack replacement 194 |

195 | 196 | This is an experimental feature that despite being fully functional it is still under development and research, so make sure to test your code if you decide to integrate this technique on it. 197 | 198 | ## How to use it 199 | 200 | To use the stack replacement functionality you should add the following line to your `cargo.toml` and compile on `release` mode: 201 | 202 | ```rust 203 | [dependencies] 204 | unwinder = {version = "0.1.4", features = ["Experimental"]} 205 | ``` 206 | 207 | The main functionality of this feature has been wrapped in the following macros: 208 | * The `start_stack_replacement!()`/`end_replacement!()` pair of macros indicates `unwinder` to start/end the stack replacement process. These two macros must be called in your code's entry point (e.g. in your dll's exported functions). 209 | * The `replace_and_continue!()`/`restore!()` pair of macros performs the replacement/restoration of the last return address. 210 | * Finally, the `replace_and_call!()`/`replace_and_syscall!()` pair of macros are used to perform stack replacement when we want to call functions outside of the current module (e.g. when using Windows API or calling any other dll's code). Both of these macros will return a *mut c_void containing the value returned by the function called this way (i.e. they operate the same way as described for the macros `call_function` and `indirect_syscall` used to execute SilentMoonWalk). 211 | 212 | To use these macros it is required to import `std::ffi::c_void` data type. 213 | All the functions using any of these macros should be labeled with the `#[no_mangle]` or `#[inline(never)]` attributes to prevent the rust compiler from inlining them during the optimization process. 214 | 215 | Before diving into a practical example showing how to use all of this stuff, just a quick inspection of the `replace_and_call`/`replace_and_syscall` pair of macros and how to pass them the expected arguments. 216 | 217 | ## replace_and_call 218 | 219 | This macro is used to call any desired function outside of the current module with a clean call stack while using stack replacement. 220 | The macro expects the following parameters: 221 | * The first parameter is the memory address of the function to call. This parameter should be passed as a `usize`, `isize` or a pointer. 222 | * The following parameters are those arguments to send to the specified function. They follow the same rules specified in the [Parameter passing](#Parameter-passing) section. 223 | 224 | ## replace_and_syscall 225 | 226 | This macro is used to perform any desired indirect syscall with a clean call stack while using stack replacement. 227 | The macro expects the following parameters: 228 | * The first parameter is a string that contains the name of the NT function whose syscall you want to execute. 229 | * The following parameters are those arguments to send to the NT function. They follow the same rules specified in the [Parameter passing](#Parameter-passing) section. 230 | 231 | 232 | ## Example 233 | 234 | I think the best way to show how these macros are used is through a practical example. Let's suppose we are creating a dll that will be **reflectively injected** to memory. This dll will export two functions `ExportA` and `ExportB`, so we will consider these two functions as the module's entry points. Both of them must call `start_stack_replacement` macro right at the beginning and also they must call the reverse `end_replacement` macro before returning. The `start_stack_replacement` macro **expects as argument the module's base address**, or you can pass 0 if you dont know that address at runtime, the macro will try to figure it out by itself. 235 | 236 | ```rust 237 | #[no_mangle] 238 | fn ExportedA(base_address: usize) -> bool 239 | { 240 | unwinder::start_replacement!(base_address); 241 | ... 242 | unwinder::end_replacement!(); 243 | 244 | true 245 | } 246 | 247 | #[no_mangle] 248 | fn ExportedB() -> bool 249 | { 250 | unwinder::start_replacement!(0); 251 | ... 252 | unwinder::end_replacement!(); 253 | 254 | true 255 | } 256 | ``` 257 | Starting the stack replacement process involves the manual crafting of a new stack that will be used until the `end_replacement` macro is called. The following picture illustrates what is going on under the hood: 258 | 259 |

260 | Stack replacement 261 |

262 | 263 | Although theoretically it would not be necessary to start a new stack from scratch, I've decided to implement the process this way to ensure stability and to prevent anything from breaking. 264 | 265 | Now, let's assume that our `ExportedA` function makes several calls to another two internal functions. These two internal functions are responsible for replacing/restoring the original return address that will point to some place within `ExportedA`, breaking the call stack unless we take care of it. This replacement process involves wrapping our internal function's code between the `replace_and_continue` and `restore` macros: 266 | 267 | ```rust 268 | #[no_mangle] 269 | fn ExportedA(base_address: usize) -> bool 270 | { 271 | unwinder::start_replacement!(base_address); 272 | let ret_a = internal_a(); 273 | let ret_b = internal_b(ret_a); 274 | unwinder::end_replacement!(); 275 | 276 | ret_b 277 | } 278 | 279 | #[inline(never)] // This attribute is mandatory 280 | fn internal_a() -> bool 281 | { 282 | unwinder::replace_and_continue(); 283 | ... 284 | unwinder::restore(); 285 | 286 | some_value 287 | } 288 | 289 | #[inline(never)] // This attribute is mandatory 290 | fn internal_b(value: bool) -> bool 291 | { 292 | unwinder::replace_and_continue(); 293 | ... 294 | unwinder::restore(); 295 | 296 | some_value 297 | } 298 | ``` 299 | 300 | Finally, both `internal_a` and `internal_b` functions make use of some Windows API functionality. To keep the unwindable call stack, these calls should be performed through the `replace_and_call` (normal call) or `replace_and_syscall` (indirect syscall) macros. 301 | 302 | ```rust 303 | #[no_mangle] // This attribute is mandatory 304 | fn ExportedA(base_address: usize) -> bool 305 | { 306 | unwinder::start_replacement!(base_address); 307 | let ret_a = internal_a(); 308 | let ret_b = internal_b(ret_a); 309 | unwinder::end_replacement!(); 310 | 311 | ret_b 312 | } 313 | 314 | #[inline(never)] // This attribute is mandatory 315 | fn internal_a() -> bool 316 | { 317 | unwinder::replace_and_continue(); 318 | ... 319 | let module_name = "advapi32.dll"; 320 | let module_name = CString::new(module_name.to_string()).expect(""); 321 | let module_name_ptr: *mut u8 = std::mem::transmute(module_name.as_ptr()); 322 | let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll"); 323 | let load_library = dinvoke_rs::dinvoke::get_function_address(k32, "LoadLibraryA"); 324 | let ret = unwinder::replace_and_call!(load_library, module_name_ptr); // Load a dll with an unwindable call stack 325 | println!("advapi.dll base address: 0x{:x}", ret as usize); 326 | ... 327 | unwinder::restore(); 328 | 329 | some_value 330 | } 331 | 332 | #[inline(never)] // This attribute is mandatory 333 | fn internal_b(value: bool) -> bool 334 | { 335 | unwinder::replace_and_continue(); 336 | ... 337 | let large = 0xFFFFFFFFFF676980 as u64; // Sleep one second 338 | let large: *mut i64 = std::mem::transmute(&large); 339 | let alertable = false; 340 | let ntstatus = unwinder::replace_and_syscall!("NtDelayExecution", alertable, large); 341 | println!("ntstatus: {:x}", ntstatus as usize); 342 | ... 343 | unwinder::restore(); 344 | 345 | some_value 346 | } 347 | ``` 348 | 349 | ## Remarks 350 | 351 | Since this is an under development feature, some stuff must be taken into account: 352 | * If you are removing your PE's headers during the loading process, you must pass to the `start_stack_replace` macro the module's base address. Right now, it won't be able to find it by itself (to be solved in the next update). 353 | * In case you are wondering, stack replacement uses the same combination of `jmp rbx` + concealment frame as the SilentMoonWalk technique. This happens only when using `replace_and_call` and `replace_and_syscall` macros and it is planned to be changed in the next update. 354 | * Both `replace_and_call` and `replace_and_syscall` macros return a `*mut c_void` that can be used to retrieve the value returned by the function executed through them. This is the same behaviour as the one described for the `call_function` and `indirect_syscall` macros. 355 | * `replace_and_call` and `replace_and_syscall` macros allow up to 11 arguments. 356 | 357 | Please report me any bug that may arise when using this feature. -------------------------------------------------------------------------------- /unwinder/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate litcrypt2; 3 | use_litcrypt!(); 4 | 5 | #[cfg(feature = "Experimental")] 6 | use std::collections::HashMap; 7 | #[cfg(feature = "Experimental")] 8 | use std::sync::{Arc, Mutex}; 9 | #[cfg(feature = "Experimental")] 10 | use std::mem::size_of; 11 | use std::{ffi::c_void, mem::transmute, ptr::{self}, vec}; 12 | use nanorand::{WyRand, Rng}; 13 | #[cfg(feature = "Experimental")] 14 | use lazy_static::lazy_static; 15 | #[cfg(feature = "Experimental")] 16 | use windows::Win32::{Foundation::HANDLE, System::{Memory::MEMORY_BASIC_INFORMATION, SystemInformation::SYSTEM_INFO}}; 17 | use windows::Win32::System::Threading::GetCurrentThread; 18 | use bitreader::BitReader; 19 | #[cfg(feature = "Experimental")] 20 | use dinvoke_rs::data::{PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, PAGE_READONLY, PAGE_READWRITE}; 21 | use dinvoke_rs::data::{RuntimeFunction, ADD_RSP, JMP_RBX, PVOID, TLS_OUT_OF_INDEXES, UNW_FLAG_CHAININFO, UNW_FLAG_EHANDLER}; 22 | 23 | extern "C" 24 | { 25 | fn spoof_call(structure: PVOID) -> PVOID; 26 | #[cfg(feature = "Experimental")] 27 | fn spoof_call2(structure: PVOID) -> PVOID; 28 | pub fn get_current_rsp() -> usize; 29 | #[cfg(feature = "Experimental")] 30 | pub fn get_current_function_address() -> usize; 31 | #[cfg(feature = "Experimental")] 32 | pub fn start_replacement(structure: PVOID); 33 | #[cfg(feature = "Experimental")] 34 | pub fn end_replacement(structure: PVOID); 35 | } 36 | 37 | #[cfg(feature = "Experimental")] 38 | #[derive(Clone, Copy, Default)] 39 | #[repr(C)] 40 | pub struct NewStackInfo 41 | { 42 | rtladdr: usize, 43 | rtlsize: usize, 44 | baseaddr: usize, 45 | basesize: usize, 46 | current_size: usize, 47 | total_size: usize, 48 | } 49 | 50 | #[repr(C)] 51 | struct Configuration 52 | { 53 | god_gadget: usize, // Unsed atm 54 | rtl_unwind_address: usize, // Unsed atm 55 | rtl_unwind_target: usize, // Unsed atm 56 | stub: usize, // Unsed atm 57 | first_frame_function_pointer: PVOID, 58 | second_frame_function_pointer: PVOID, 59 | jmp_rbx_gadget: PVOID, 60 | add_rsp_xgadget: PVOID, 61 | first_frame_size: usize, 62 | second_frame_size: usize, 63 | jmp_rbx_gadget_frame_size: usize, 64 | add_rsp_xgadget_frame_size: usize, 65 | stack_offset_where_rbp_is_pushed: usize, 66 | spoof_function_pointer: PVOID, 67 | return_address: PVOID, 68 | nargs: usize, 69 | arg01: PVOID, 70 | arg02: PVOID, 71 | arg03: PVOID, 72 | arg04: PVOID, 73 | arg05: PVOID, 74 | arg06: PVOID, 75 | arg07: PVOID, 76 | arg08: PVOID, 77 | arg09: PVOID, 78 | arg10: PVOID, 79 | arg11: PVOID, 80 | syscall: u32, 81 | syscall_id: u32 82 | } 83 | 84 | static mut TLS_INDEX: u32 = 0; 85 | #[cfg(feature = "Experimental")] 86 | static mut RUNTIME_INFO: (usize, u32) = (0, 0); 87 | #[cfg(feature = "Experimental")] 88 | static mut BASE_ADDRESS: usize = 0; // Current module's base address 89 | #[cfg(feature = "Experimental")] 90 | lazy_static! { 91 | // Original return address -> key 92 | // Replacement return address -> value 93 | pub static ref MAP: Arc>> = Arc::new(Mutex::new(HashMap::default())); 94 | } 95 | 96 | /// The `replace_and_continue` macro is designed to spoof the last return address in the call stack to conceal the presence of anomalous entries. 97 | /// 98 | /// This macro calculates the size of the last frame at runtime and searches for another function with the same size in a legitimately loaded DLL within 99 | /// the current process. The legitimate function is used then as replacement return address, allowing to hide the last entry in the call stack. 100 | /// 101 | /// The real replaced return addresses are stored in an internal structure so they can be restored later when the `restore` macro is called. 102 | /// 103 | /// To use this macro, the `start_replacement` macro must have been called previously to initialize the necessary structures. Any 104 | /// function calling this macro should declare the `#[inline(never)]` attribute (exported functions through the `#[no_mangle]` attribute doest not need to 105 | /// additionally declare the inline attribute). 106 | /// 107 | /// # Example 108 | /// 109 | /// ```rust 110 | /// #[inline(never)] // This attribute is mandatory for any function calling this macro 111 | /// fn another_function() -> bool 112 | /// { 113 | /// unwinder::replace_and_continue(); 114 | /// ... 115 | /// unwinder::restore(); 116 | /// 117 | /// true 118 | /// } 119 | /// 120 | /// #[no_mangle] 121 | /// fn your_program_entry(base_address: usize) -> bool 122 | /// { 123 | /// unwinder::start_replacement!(base_address); 124 | /// let r = another_function(); 125 | /// unwinder::end_replacement!(); 126 | /// 127 | /// r 128 | /// } 129 | /// ``` 130 | /// 131 | #[cfg(feature = "Experimental")] 132 | #[macro_export] 133 | macro_rules! replace_and_continue { 134 | 135 | () => {{ 136 | unsafe 137 | { 138 | 139 | // We get the current function's frame size by iterating the current module's 140 | // unwinding info looking for the function's address. 141 | let current_function_address = $crate::get_current_function_address(); 142 | let current_function_size = $crate::get_frame_size_from_address(current_function_address); 143 | 144 | // We get the current position of RSP, which combined with the current frame size 145 | // allows us to obtain the position in the stack of the return address to spoof. 146 | let current_rsp = $crate::get_current_rsp() as *mut usize; 147 | let n = current_function_size/8; 148 | let return_address_ptr = current_rsp.add(n as _); 149 | let return_address: usize = *return_address_ptr; 150 | 151 | // We check if the original return address has already being processed, in which case the pre calculated 152 | // replacement return address is retrieved. Otherwise, We calculate a replacement return address. 153 | let map = std::sync::Arc::clone(&$crate::MAP); 154 | let mut map = map.lock().unwrap(); 155 | if let Some(h) = map.get_mut(&return_address) { 156 | *return_address_ptr = *h; 157 | } 158 | else 159 | { 160 | // We get the last frame size by iterating the current module's unwinding info. 161 | let replacement_function_size = $crate::get_frame_size_from_address(return_address); 162 | let mut black_list: Vec = vec![]; 163 | for (_, value) in map.iter() { 164 | black_list.push(*value); 165 | } 166 | 167 | // We look for a legitimate return address contained in a function with the same frame size as that of 168 | // as previously calculated. Once found a legitimate replacement, the return address is spoofed. 169 | let replacement_frame = $crate::get_frame_of_size(replacement_function_size, black_list, false); 170 | *return_address_ptr = replacement_frame; 171 | map.insert(return_address, replacement_frame); 172 | } 173 | } 174 | }} 175 | } 176 | 177 | /// The `restore` macro is the inverse of the `replace_and_continue` macro. 178 | /// 179 | /// This macro should be called at the end of all functions that have called the `replace_and_continue` macro. 180 | /// Its primary objective is to restore the return address to its original value, allowing the program to continue 181 | /// normal execution. 182 | /// 183 | /// Similar to the `replace_and_continue` macro, any function that calls the `restore` macro must declare the attribute 184 | /// `#[inline(never)]`, unless the function is exported using the `#[no_mangle]` attribute, in which case the `#[inline(never)]` 185 | /// attribute is not required. 186 | /// 187 | /// # Example 188 | /// 189 | /// ```rust 190 | /// #[inline(never)] // This attribute is mandatory for any function calling this macro 191 | /// fn another_function() -> bool 192 | /// { 193 | /// unwinder::replace_and_continue(); 194 | /// ... 195 | /// unwinder::restore(); 196 | /// 197 | /// true 198 | /// } 199 | /// 200 | /// #[no_mangle] 201 | /// fn your_program_entry(base_address: usize) -> bool 202 | /// { 203 | /// unwinder::start_replacement!(base_address); 204 | /// let r = another_function(); 205 | /// unwinder::end_replacement!(); 206 | /// 207 | /// r 208 | /// } 209 | /// ``` 210 | #[cfg(feature = "Experimental")] 211 | #[macro_export] 212 | macro_rules! restore { 213 | 214 | () => {{ 215 | unsafe 216 | { 217 | // We calculate the position in the stack where the return address is located and we 218 | // restore its value to that of the original address, allowing the program to continue the normal execution. 219 | let current_function_address = $crate::get_current_function_address(); 220 | let current_function_size = $crate::get_frame_size_from_address(current_function_address); 221 | 222 | let current_rsp = $crate::get_current_rsp() as *mut usize; 223 | let n = current_function_size/8; 224 | let return_address_ptr = current_rsp.add(n as _); 225 | let return_address: usize = *return_address_ptr; 226 | 227 | let map = std::sync::Arc::clone(&$crate::MAP); 228 | let mut map = map.lock().unwrap(); 229 | for (key, value) in map.iter() 230 | { 231 | if *value == return_address 232 | { 233 | *return_address_ptr = *key; 234 | break; 235 | } 236 | } 237 | } 238 | }} 239 | } 240 | 241 | /// The `start_replacement` macro initiates the stack replacement process. 242 | /// 243 | /// This macro expects a single parameter of type `usize`. This parameter represents 244 | /// the base memory address of the current module, or zero if the base address is unknown. 245 | /// 246 | /// If a non-zero memory address is provided, the macro will use it to locate information 247 | /// regarding the module. If zero is passed, the macro will perform its own process to 248 | /// determine the base memory address. 249 | /// 250 | /// The primary goal of this macro is to initiate the stack replacement process. 251 | /// To achieve this, it initializes all necessary structures and creates a clean stack 252 | /// on top of the existing one. This new stack will be used by the program until the 253 | /// `end_replacement` macro is called, allowing to have a clean call stack during the execution of your code. 254 | /// 255 | /// # Parameters 256 | /// 257 | /// - `base_address: usize`: The base memory address of the current module or zero. 258 | /// 259 | /// # Example 260 | /// 261 | /// ```rust 262 | /// #[no_mangle] 263 | /// fn your_program_entry(base_address: usize) -> bool 264 | /// { 265 | /// unwinder::start_replacement!(base_address); 266 | /// ... 267 | /// unwinder::end_replacement!(); 268 | /// 269 | /// true 270 | /// } 271 | /// ``` 272 | #[cfg(feature = "Experimental")] 273 | #[macro_export] 274 | macro_rules! start_replacement { 275 | ($x:expr) => 276 | { 277 | unsafe 278 | { 279 | // The start_stack_replacement function locates the current module's base address if it has not 280 | // been specified when calling the macro. With that address, it locates the module's unwind data 281 | // to be used later on. 282 | let start = $crate::start_stack_replacement($x); 283 | 284 | // We get the current function's frame size and other data required to create a new stack on top 285 | // of the existing one that will be used by this and any nested function being called from now on. 286 | let address = $crate::get_current_function_address(); 287 | let mut base_pointer = false; // This variable is unused at the moment. 288 | let current_function_size = $crate::get_frame_size_from_address(address); 289 | 290 | let structure = $crate::get_info_structure(current_function_size as usize); 291 | let structure: *mut std::ffi::c_void = std::mem::transmute(&structure); 292 | // The start_replacement stub creates the new stack and moves the current frame's contents to their new locations. 293 | $crate::start_replacement(structure); 294 | } 295 | } 296 | } 297 | 298 | /// The `end_replacement` macro finalizes the stack replacement process. 299 | /// 300 | /// Its primary function is to restore the stack state to what it was before 301 | /// the `start_replacement` macro was called, allowing to continue the execution outside 302 | /// of the current module. 303 | /// 304 | /// Calling this macro ends the stack replacement process, ensuring that all necessary 305 | /// structures and states are properly restored. 306 | /// 307 | /// This macro should be called at the end of any function that made the call to the 308 | /// `start_replacement` macro. 309 | /// 310 | /// # Example 311 | /// 312 | /// ```rust 313 | /// #[no_mangle] 314 | /// fn your_program_entry(base_address: usize) -> bool 315 | /// { 316 | /// unwinder::start_replacement!(base_address); 317 | /// ... 318 | /// unwinder::end_replacement!(); 319 | /// 320 | /// true 321 | /// } 322 | /// ``` 323 | #[cfg(feature = "Experimental")] 324 | #[macro_export] 325 | macro_rules! end_replacement { 326 | () => 327 | { 328 | unsafe 329 | { 330 | // We just remove the previously crafted new stack, restoring the previous state. 331 | let address = $crate::get_current_function_address(); 332 | let current_function_size = $crate::get_frame_size_from_address(address); 333 | 334 | let structure = $crate::get_info_structure(current_function_size as usize); 335 | let structure: *mut std::ffi::c_void = std::mem::transmute(&structure); 336 | $crate::end_replacement(structure); 337 | } 338 | } 339 | } 340 | 341 | /// The `replace_and_call` macro is used to apply stack replacement when calling a function that resides outside of the current module. 342 | /// 343 | /// This macro acts as a gateway, spoofing the last entry in the call stack before jumping to the memory address of the specified function. 344 | /// 345 | /// To use this macro, the `start_replacement` macro must have been called previously to initialize the necessary structures. Any 346 | /// function calling this macro should declare the `#[inline(never)]` attribute (exported functions using the `#[no_mangle]` attribute doest not need to 347 | /// additionally declare the inline attribute). 348 | /// 349 | /// The first parameter the macro expects is the memory address of the function to be called after applying the stack replacement process. 350 | /// The rest of the parameters are the arguments to be passed to the specified function. 351 | /// 352 | /// The return value of the macro is the same as the return value of the function specified in the first parameter. 353 | /// 354 | /// # Parameters 355 | /// 356 | /// - `function_address: usize`: The memory address of the function to be called. 357 | /// - `args...`: The arguments to be passed to the function. 358 | /// 359 | /// # Example 360 | /// 361 | /// ```rust 362 | /// #[inline(never)] // This attribute is mandatory for any function calling this macro 363 | /// fn another_function() -> bool 364 | /// { 365 | /// unwinder::replace_and_continue(); 366 | /// ... 367 | /// let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll"); 368 | /// let sleep = dinvoke_rs::dinvoke::get_function_address(k32, "Sleep"); // Memory address of kernel32.dll!Sleep() 369 | /// let miliseconds = 1000i32; 370 | /// unwinder::replace_and_call!(sleep, false, miliseconds); 371 | /// ... 372 | /// unwinder::restore(); 373 | /// 374 | /// true 375 | /// } 376 | /// 377 | /// #[no_mangle] 378 | /// fn your_program_entry(base_address: usize) -> bool 379 | /// { 380 | /// unwinder::start_replacement!(base_address); 381 | /// let r = another_function(); 382 | /// unwinder::end_replacement!(); 383 | /// 384 | /// r 385 | /// } 386 | /// ``` 387 | #[cfg(feature = "Experimental")] 388 | #[macro_export] 389 | macro_rules! replace_and_call { 390 | 391 | ($($x:expr),*) => {{ 392 | 393 | unsafe 394 | { 395 | let mut temp_vec = Vec::new(); 396 | $( 397 | let temp = $x as usize; // This is meant to convert integers with smaller size than 8 bytes 398 | let pointer: *mut std::ffi::c_void = std::mem::transmute(temp); 399 | temp_vec.push(pointer); 400 | )* 401 | 402 | let res = $crate::replace_and_call(temp_vec, false, 0); 403 | res 404 | } 405 | }} 406 | } 407 | 408 | /// The `replace_and_syscall` macro is used to apply stack replacement when performing an indirect syscall. 409 | /// 410 | /// This macro acts as a gateway, spoofing the last entry in the call stack before executing the indirect syscall 411 | /// to the specified NT function. 412 | /// 413 | /// To use this macro, the `start_replacement` macro must have been called previously in another function to initialize the necessary structures. Any 414 | /// function calling this macro should declare the `#[inline(never)]` attribute (exported functions using the `#[no_mangle]` attribute doest not need to 415 | /// additionally declare the inline attribute). 416 | /// 417 | /// The first parameter the macro expects is the name of the NT function whose indirect syscall is to be executed. 418 | /// The rest of the parameters are the arguments to be passed to the specified NT function. 419 | /// 420 | /// The return value of the macro is the `NTSTATUS` value returned by the NT function. 421 | /// 422 | /// # Parameters 423 | /// 424 | /// - `nt_function_name: &str`: The name of the NT function to be called. 425 | /// - `args...`: The arguments to be passed to the NT function. 426 | /// 427 | /// # Example 428 | /// 429 | /// ```rust 430 | /// #[inline(never)] // This attribute is mandatory for any function calling this macro 431 | /// fn another_function() -> bool 432 | /// { 433 | /// unwinder::replace_and_continue(); 434 | /// ... 435 | /// let large = 0x8000000000000000 as u64; // Sleep indefinitely 436 | /// let large: *mut i64 = std::mem::transmute(&large); 437 | /// let alertable = false; 438 | /// let ntstatus = unwinder::replace_and_syscall!("NtDelayExecution", alertable, large); 439 | /// println!("ntstatus: {:x}", ntstatus as i32); 440 | /// ... 441 | /// unwinder::restore(); 442 | /// 443 | /// true 444 | /// } 445 | /// 446 | /// #[no_mangle] 447 | /// fn your_program_entry(base_address: usize) -> bool 448 | /// { 449 | /// unwinder::start_replacement!(base_address); 450 | /// let r = another_function(); 451 | /// unwinder::end_replacement!(); 452 | /// 453 | /// r 454 | /// } 455 | /// ``` 456 | #[cfg(feature = "Experimental")] 457 | #[macro_export] 458 | macro_rules! replace_and_syscall { 459 | 460 | ($a:expr, $($x:expr),*) => {{ 461 | 462 | unsafe 463 | { 464 | let mut temp_vec = Vec::new(); 465 | let r = -1isize; 466 | let mut res: *mut std::ffi::c_void = std::mem::transmute(r); 467 | let t = $crate::prepare_syscall($a); 468 | if t.0 != u32::MAX 469 | { 470 | let p: *mut std::ffi::c_void = std::mem::transmute(t.1); 471 | temp_vec.push(p); 472 | $( 473 | let temp = $x as usize; // This is meant to convert integers with smaller size than 8 bytes 474 | let pointer: *mut std::ffi::c_void = std::mem::transmute(temp); 475 | temp_vec.push(pointer); 476 | )* 477 | 478 | res = $crate::replace_and_call(temp_vec, true, t.0); 479 | } 480 | 481 | res 482 | } 483 | }} 484 | } 485 | 486 | /// This function is reponsible of retrieving all the information required to create the new 487 | /// stack when the stack replacemnent process is started. 488 | /// The returned struct gathers all the required information, including the first three frames sizes, the new stack 489 | /// total size and the first two return addresses (RtlUserThreadStart+0x21 and BaseThreadInitThunk+0x14). 490 | #[cfg(feature = "Experimental")] 491 | #[inline(never)] 492 | pub fn get_info_structure(current_size: usize) -> NewStackInfo 493 | { 494 | unsafe 495 | { 496 | let ntdll = dinvoke_rs::dinvoke::get_module_base_address(&lc!("ntdll.dll")); 497 | let kernel32 = dinvoke_rs::dinvoke::get_module_base_address(&lc!("kernel32.dll")); 498 | 499 | let rtl_user_thread_start = dinvoke_rs::dinvoke::get_function_address(ntdll, &lc!("RtlUserThreadStart")); 500 | let size_rtl: i32 = get_frame_size_from_address_any_module(ntdll as _, rtl_user_thread_start as _); 501 | let rtl_user_thread_start_address = rtl_user_thread_start as usize + 0x21 as usize; 502 | 503 | let base_thread_init_thunk = dinvoke_rs::dinvoke::get_function_address(kernel32, &lc!("BaseThreadInitThunk")); 504 | let size_base = get_frame_size_from_address_any_module(kernel32 as _, base_thread_init_thunk as _); 505 | let base_thread_init_thunk_address = base_thread_init_thunk as usize + 0x14 as usize; 506 | 507 | let mut stack_info: NewStackInfo = std::mem::zeroed(); 508 | stack_info.rtladdr = rtl_user_thread_start_address; 509 | stack_info.rtlsize = size_rtl as usize; 510 | stack_info.baseaddr = base_thread_init_thunk_address; 511 | stack_info.basesize = size_base as usize; 512 | stack_info.current_size = current_size; 513 | stack_info.total_size = size_rtl as usize + size_base as usize + current_size + 16 ; // 16 (two return addresses) 514 | 515 | stack_info 516 | } 517 | } 518 | 519 | #[cfg(feature = "Experimental")] 520 | #[inline(never)] 521 | pub fn start_stack_replacement(base_address: usize) -> bool 522 | { 523 | unsafe 524 | { 525 | if BASE_ADDRESS != 0 && RUNTIME_INFO != (0,0) { 526 | return true; 527 | } 528 | 529 | let runtime_info = get_current_runtime_table(base_address); 530 | if runtime_info.1 == 0 { 531 | return false; 532 | } 533 | 534 | RUNTIME_INFO = (runtime_info.0 as _, runtime_info.1); 535 | true 536 | } 537 | } 538 | 539 | /// Given a module's base address and the address of one of its functions, it will iterate over the module's unwind data 540 | /// in order to get the function's frame size in bytes. 541 | /// In case that the function has not been located, it will return 0. 542 | #[cfg(feature = "Experimental")] 543 | #[inline(never)] 544 | pub fn get_frame_size_from_address_any_module(mut module: usize, address: usize) -> i32 545 | { 546 | unsafe 547 | { 548 | if module == 0 549 | { 550 | let module_handle = 0usize; 551 | let module_handle: *mut usize = std::mem::transmute(&module_handle); 552 | dinvoke_rs::dinvoke::get_module_handle_ex_a(0x00000004|0x00000002 , address as _, module_handle); 553 | if *module_handle == 0 { 554 | return 0; 555 | } else { 556 | module = *module_handle; 557 | } 558 | } 559 | 560 | let exception_directory = get_runtime_table(module as *mut _); 561 | let mut rt = exception_directory.0; 562 | if rt == ptr::null_mut() { 563 | return 0; 564 | } 565 | 566 | let items = exception_directory.1 / 12; 567 | let mut count = 0; 568 | while count < items 569 | { 570 | let function_start_address = (module + (*rt).begin_addr as usize) as *mut u8; 571 | let function_end_address = (module + (*rt).end_addr as usize) as *mut u8; 572 | if address >= function_end_address as usize || address < function_start_address as usize 573 | { 574 | rt = rt.add(1); 575 | count += 1; 576 | continue; 577 | } 578 | else 579 | { 580 | let size = get_frame_size_normal(module, *rt, true, &mut false); 581 | return size; 582 | } 583 | 584 | } 585 | 586 | 0 587 | } 588 | } 589 | 590 | /// Given a function's address, it will iterate over the current module's unwind data 591 | /// in order to get the function's frame size in bytes. 592 | /// In case that the function has not been located, it will return 0. 593 | #[cfg(feature = "Experimental")] 594 | #[inline(never)] 595 | pub fn get_frame_size_from_address(address: usize) -> i32 596 | { 597 | unsafe 598 | { 599 | let items = RUNTIME_INFO.1 / 12; 600 | let mut count = 0; 601 | let mut rt = RUNTIME_INFO.0 as *mut RuntimeFunction; 602 | 603 | while count < items 604 | { 605 | let runtime_function = *rt; 606 | if address < BASE_ADDRESS + runtime_function.begin_addr as usize || runtime_function.begin_addr == 0 || runtime_function.end_addr == 0 { 607 | break; 608 | } 609 | 610 | if address >= BASE_ADDRESS + runtime_function.end_addr as usize 611 | { 612 | rt = rt.add(1); 613 | count += 1; 614 | continue; 615 | } 616 | else 617 | { 618 | let size = get_frame_size_normal(BASE_ADDRESS, runtime_function, true, &mut false); 619 | return size; 620 | } 621 | } 622 | 623 | 0 624 | } 625 | } 626 | 627 | 628 | /// Given a frame size, this function will look for a function in kernel32.dll/kernelbase.dll/ntdll.dll with the same 629 | /// frame size. In case it finds it, it will return its memory address; otherwise it returns 0. 630 | /// 631 | /// The black_list argument allows to specify a list of functions that shouldn't be returned when calling this function. 632 | /// The ignore argument should be set to false in case we want to discard any function that sets a base pointer (UWOP_SET_FPREG unwind code). 633 | #[cfg(feature = "Experimental")] 634 | #[inline(never)] 635 | pub fn get_frame_of_size(desired_size: i32, black_list: Vec, ignore: bool) -> usize 636 | { 637 | unsafe 638 | { 639 | let k32: usize = dinvoke_rs::dinvoke::get_module_base_address(&lc!("kernel32.dll")) as usize; 640 | 641 | let exception_directory = get_runtime_table(k32 as *mut _); 642 | let mut rt = exception_directory.0; 643 | if rt == ptr::null_mut() { 644 | return 0; 645 | } 646 | 647 | let items = exception_directory.1 / 12; 648 | let mut rng = WyRand::new(); 649 | let rt_offset = rng.generate_range(0..(items/2)); 650 | rt = rt.add(rt_offset as usize); 651 | let mut count = rt_offset; 652 | while count < items 653 | { 654 | let runtime_function = *rt; 655 | let size = get_frame_size_normal(k32, runtime_function, ignore, &mut false); 656 | if size == desired_size 657 | { 658 | let random_offset = generate_random_offset(k32, runtime_function); 659 | let final_address = k32 + random_offset as usize; 660 | 661 | if random_offset != 0 && !black_list.contains(&final_address){ 662 | return final_address; 663 | } 664 | } 665 | 666 | rt = rt.add(1); 667 | count += 1; 668 | } 669 | 670 | let kernelbase = dinvoke_rs::dinvoke::get_module_base_address(&lc!("kernelbase.dll")) as usize; 671 | 672 | let exception_directory = get_runtime_table(kernelbase as *mut _); 673 | let mut rt = exception_directory.0; 674 | if rt == ptr::null_mut() { 675 | return 0; 676 | } 677 | 678 | let items = exception_directory.1 / 12; 679 | let mut rng = WyRand::new(); 680 | let rt_offset = rng.generate_range(0..(items/2)); 681 | rt = rt.add(rt_offset as usize); 682 | let mut count = rt_offset; 683 | while count < items 684 | { 685 | let runtime_function = *rt; 686 | let size = get_frame_size_normal(kernelbase, runtime_function, ignore, &mut false); 687 | if size == desired_size 688 | { 689 | let random_offset = generate_random_offset(kernelbase, runtime_function); 690 | let final_address = kernelbase + random_offset as usize; 691 | 692 | if random_offset != 0 && !black_list.contains(&final_address){ 693 | return final_address; 694 | } 695 | } 696 | 697 | rt = rt.add(1); 698 | count += 1; 699 | } 700 | 701 | let ntdll = dinvoke_rs::dinvoke::get_module_base_address(&lc!("ntdll.dll")) as usize; 702 | 703 | let exception_directory = get_runtime_table(ntdll as *mut _); 704 | let mut rt = exception_directory.0; 705 | if rt == ptr::null_mut() { 706 | return 0; 707 | } 708 | 709 | let items = exception_directory.1 / 12; 710 | let mut rng = WyRand::new(); 711 | let rt_offset = rng.generate_range(0..(items/2)); 712 | rt = rt.add(rt_offset as usize); 713 | let mut count = rt_offset; 714 | while count < items 715 | { 716 | let runtime_function = *rt; 717 | let size = get_frame_size_normal(ntdll, runtime_function, ignore, &mut false); 718 | if size == desired_size 719 | { 720 | let random_offset = generate_random_offset(ntdll, runtime_function); 721 | let final_address = ntdll + random_offset as usize; 722 | 723 | if random_offset != 0 && !black_list.contains(&final_address){ 724 | return final_address; 725 | } 726 | } 727 | 728 | rt = rt.add(1); 729 | count += 1; 730 | } 731 | 732 | 0 733 | } 734 | } 735 | 736 | /// Spoofs the previous return address and jumps to the stub that contains the assembly code responsible for 737 | /// preparing the stack/registers and make the call to the final function. 738 | #[cfg(feature = "Experimental")] 739 | #[inline(never)] 740 | pub fn replace_and_call(mut args: Vec<*mut c_void>, is_syscall: bool, id: u32) -> *mut c_void 741 | { 742 | unsafe 743 | { 744 | if is_syscall && (id == u32::MAX) { 745 | return ptr::null_mut(); 746 | } 747 | 748 | let mut config: Configuration = std::mem::zeroed(); 749 | let mut black_list: Vec<(u32,u32)> = vec![]; 750 | let kernelbase = dinvoke_rs::dinvoke::get_module_base_address(&lc!("kernelbase.dll")) as usize; 751 | 752 | let mut first_gadget_size = 0i32; 753 | let first_gadget_addr = find_gadget(kernelbase, &mut first_gadget_size, 0, &mut black_list); 754 | 755 | let mut second_gadget_size = 0i32; 756 | let second_gadget_addr = find_gadget(kernelbase, &mut second_gadget_size, 1, &mut black_list); 757 | 758 | config.jmp_rbx_gadget = first_gadget_addr as *mut _; 759 | config.jmp_rbx_gadget_frame_size = first_gadget_size as usize; 760 | config.add_rsp_xgadget = second_gadget_addr as *mut _; 761 | config.add_rsp_xgadget_frame_size = second_gadget_size as usize; 762 | config.spoof_function_pointer = args.remove(0); 763 | config.syscall = is_syscall as u32; 764 | config.syscall_id = id; 765 | 766 | let mut args_number = args.len(); 767 | config.nargs = args_number; 768 | 769 | while args_number > 0 770 | { 771 | match args_number 772 | { 773 | 11 => config.arg11 = args[args_number-1], 774 | 10 => config.arg10 = args[args_number-1], 775 | 9 => config.arg09 = args[args_number-1], 776 | 8 => config.arg08 = args[args_number-1], 777 | 7 => config.arg07 = args[args_number-1], 778 | 6 => config.arg06 = args[args_number-1], 779 | 5 => config.arg05 = args[args_number-1], 780 | 4 => config.arg04 = args[args_number-1], 781 | 3 => config.arg03 = args[args_number-1], 782 | 2 => config.arg02 = args[args_number-1], 783 | 1 => config.arg01 = args[args_number-1], 784 | _ => () 785 | } 786 | 787 | args_number -= 1; 788 | } 789 | 790 | let current_function_address = get_current_function_address(); 791 | let current_function_size = get_frame_size_from_address(current_function_address); 792 | let current_function_replacement = get_frame_of_size(current_function_size, Vec::default(), false) as *mut usize; 793 | config.return_address = current_function_replacement as _; 794 | 795 | let current_rsp = get_current_rsp() as *mut usize; 796 | let n = current_function_size/8; 797 | let return_address_ptr = current_rsp.add(n as _); 798 | let return_address: usize = *return_address_ptr; 799 | 800 | let replaced_function_size = get_frame_size_from_address(return_address); 801 | let replacement_frame = get_frame_of_size(replaced_function_size, Vec::default(), false); 802 | *return_address_ptr = replacement_frame; 803 | 804 | let config: PVOID = std::mem::transmute(&config); 805 | let r = spoof_call2(config); 806 | *return_address_ptr = return_address; 807 | 808 | r 809 | } 810 | } 811 | 812 | #[cfg(feature = "Experimental")] 813 | fn get_current_runtime_table(mut base_address: usize) -> (*mut dinvoke_rs::data::RuntimeFunction, u32) 814 | { 815 | unsafe 816 | { 817 | if base_address == 0 818 | { 819 | base_address = get_pe_baseaddress(); 820 | if base_address == 0 { 821 | return (ptr::null_mut(), 0); 822 | } 823 | } 824 | 825 | BASE_ADDRESS = base_address; 826 | 827 | get_runtime_table(BASE_ADDRESS as _) 828 | } 829 | } 830 | 831 | #[cfg(feature = "Experimental")] 832 | fn get_pe_baseaddress () -> usize 833 | { 834 | unsafe 835 | { 836 | let b = vec![0u8; size_of::()]; 837 | let si: *mut SYSTEM_INFO = std::mem::transmute(b.as_ptr()); 838 | dinvoke_rs::dinvoke::get_system_info(si); 839 | 840 | let main_address = get_pe_baseaddress as usize; 841 | 842 | let mut mem = 0usize; 843 | let max = (*si).lpMaximumApplicationAddress as usize; 844 | let mut previous_region = MEMORY_BASIC_INFORMATION::default(); 845 | while mem < max 846 | { 847 | let buffer = MEMORY_BASIC_INFORMATION::default(); 848 | let buffer: *mut MEMORY_BASIC_INFORMATION = std::mem::transmute(&buffer); 849 | let length = size_of::(); 850 | let _r = dinvoke_rs::dinvoke::virtual_query_ex( 851 | HANDLE(-1), 852 | mem as *const c_void, 853 | buffer, 854 | length 855 | ); 856 | 857 | let is_readable: bool = (*buffer).Protect.0 == PAGE_READONLY || (*buffer).Protect.0 == PAGE_READWRITE || (*buffer).Protect.0 == PAGE_EXECUTE_READ || (*buffer).Protect.0 == PAGE_EXECUTE_READWRITE; 858 | 859 | if is_readable 860 | { 861 | if main_address >= ((*buffer).BaseAddress as usize) && main_address <= ((*buffer).BaseAddress as usize + (*buffer).RegionSize ) { 862 | return previous_region.BaseAddress as usize; 863 | } 864 | 865 | previous_region = *buffer; 866 | } 867 | 868 | mem = (*buffer).BaseAddress as usize + (*buffer).RegionSize; 869 | } 870 | 0 871 | } 872 | } 873 | 874 | 875 | /// Call an arbitrary function with a clean call stack. 876 | /// 877 | /// This macro will make sure the thread has a clean and unwindable call stack 878 | /// before calling the specified function. 879 | /// 880 | /// The first parameter expected by the macro is the memory address of the function to call. 881 | /// The second parameter is a bool indicating whether or not keep the start function frame. If you are not 882 | /// sure about this, set it to false which always guarantees a clean call stack. 883 | /// 884 | /// The following parameters should be the arguments to pass to the specified function. 885 | /// 886 | /// The macro's return parameter is the same value returned by the specified function. 887 | /// 888 | /// # Example - Calling Sleep() with a clean call stack (using dinvoke_rs) 889 | /// 890 | /// ```ignore 891 | /// let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll"); 892 | /// let sleep = dinvoke_rs::dinvoke::get_function_address(k32, "Sleep"); // Memory address of kernel32.dll!Sleep() 893 | /// let miliseconds = 1000i32; 894 | /// unwinder::call_function!(sleep, false, miliseconds); 895 | /// ``` 896 | /// 897 | #[macro_export] 898 | macro_rules! call_function { 899 | 900 | ($($x:expr),*) => {{ 901 | 902 | unsafe 903 | { 904 | let mut temp_vec = Vec::new(); 905 | $( 906 | let temp = $x as usize; // This is meant to convert integers with smaller size than 8 bytes 907 | let pointer: *mut std::ffi::c_void = std::mem::transmute(temp); 908 | temp_vec.push(pointer); 909 | )* 910 | 911 | let res = $crate::spoof_and_call(temp_vec, false, 0); 912 | res 913 | } 914 | }} 915 | } 916 | 917 | /// Execute an indirect syscall with a clean call stack. 918 | /// 919 | /// This macro will make sure the thread has a clean and unwindable call stack 920 | /// before executing the syscall for the specified NT function. 921 | /// 922 | /// The first parameter expected by the macro is the name of the function whose syscall wants to be run. 923 | /// The second parameter is a bool indicating whether or not keep the start function frame. If you are not 924 | /// sure about this, set it to false which always guarantees a clean call stack. 925 | /// 926 | /// The following parameters should be the arguments expected by the specified syscall. 927 | /// 928 | /// The macro's return parameter is the same value returned by the syscall. 929 | /// 930 | /// # Example - Calling NtDelayExecution() as indirect syscall with a clean call stack 931 | /// 932 | /// ```ignore 933 | /// let large = 0x8000000000000000 as u64; // Sleep indefinitely 934 | /// let large: *mut i64 = std::mem::transmute(&large); 935 | /// let alertable = false; 936 | /// let ntstatus = unwinder::indirect_syscall!("NtDelayExecution", false, alertable, large); 937 | /// println!("ntstatus: {:x}", ntstatus as i32); 938 | /// ``` 939 | /// 940 | #[macro_export] 941 | macro_rules! indirect_syscall { 942 | 943 | ($a:expr, $($x:expr),*) => { 944 | 945 | unsafe 946 | { 947 | let mut temp_vec = Vec::new(); 948 | let t = $crate::prepare_syscall($a); 949 | let r = -1isize; 950 | let mut res: *mut std::ffi::c_void = std::mem::transmute(r); 951 | if t.0 != u32::MAX 952 | { 953 | let p: *mut std::ffi::c_void = std::mem::transmute(t.1); 954 | temp_vec.push(p); 955 | $( 956 | let temp = $x as usize; // This is meant to convert integers with smaller size than 8 bytes 957 | let pointer: *mut std::ffi::c_void = std::mem::transmute(temp); 958 | temp_vec.push(pointer); 959 | )* 960 | 961 | res = $crate::spoof_and_call(temp_vec, true, t.0); 962 | } 963 | 964 | res 965 | } 966 | } 967 | } 968 | 969 | /// Don't call this function directly, use call_function!() and indirect_syscall!() macros instead. 970 | pub fn spoof_and_call(mut args: Vec<*mut c_void>, is_syscall: bool, id: u32) -> *mut c_void 971 | { 972 | unsafe 973 | { 974 | if is_syscall && (id == u32::MAX) { 975 | return ptr::null_mut(); 976 | } 977 | 978 | let mut config: Configuration = std::mem::zeroed(); 979 | let mut black_list: Vec<(u32,u32)> = vec![]; 980 | let kernelbase = dinvoke_rs::dinvoke::get_module_base_address(&lc!("kernelbase.dll")) as usize; 981 | 982 | let mut first_frame_size = 0i32; 983 | let first_frame_address = find_setfpreg(kernelbase, &mut first_frame_size, &mut black_list); 984 | 985 | let mut push_offset = 0i32; 986 | let mut second_frame_size = 0i32; 987 | let second_frame_addr = find_pushrbp(kernelbase, &mut second_frame_size, &mut push_offset, &mut black_list); 988 | 989 | let mut first_gadget_size = 0i32; 990 | let first_gadget_addr = find_gadget(kernelbase, &mut first_gadget_size, 0, &mut black_list); 991 | 992 | let mut second_gadget_size = 0i32; 993 | let second_gadget_addr = find_gadget(kernelbase, &mut second_gadget_size, 1, &mut black_list); 994 | config.first_frame_function_pointer = first_frame_address as *mut _; 995 | config.first_frame_size = first_frame_size as usize; 996 | config.second_frame_function_pointer = second_frame_addr as *mut _; 997 | config.second_frame_size = second_frame_size as usize; 998 | config.jmp_rbx_gadget = first_gadget_addr as *mut _; 999 | config.jmp_rbx_gadget_frame_size = first_gadget_size as usize; 1000 | config.add_rsp_xgadget = second_gadget_addr as *mut _; 1001 | config.add_rsp_xgadget_frame_size = second_gadget_size as usize; 1002 | config.stack_offset_where_rbp_is_pushed = push_offset as usize; 1003 | config.spoof_function_pointer = args.remove(0); 1004 | config.syscall = is_syscall as u32; 1005 | config.syscall_id = id; 1006 | 1007 | let keep = args.remove(0) as usize; 1008 | let keep_start_function_frame; 1009 | if keep == 0 { 1010 | keep_start_function_frame = false; 1011 | } else { 1012 | keep_start_function_frame = true; 1013 | } 1014 | 1015 | let mut args_number = args.len(); 1016 | config.nargs = args_number; 1017 | 1018 | while args_number > 0 1019 | { 1020 | match args_number 1021 | { 1022 | 11 => config.arg11 = args[args_number-1], 1023 | 10 => config.arg10 = args[args_number-1], 1024 | 9 => config.arg09 = args[args_number-1], 1025 | 8 => config.arg08 = args[args_number-1], 1026 | 7 => config.arg07 = args[args_number-1], 1027 | 6 => config.arg06 = args[args_number-1], 1028 | 5 => config.arg05 = args[args_number-1], 1029 | 4 => config.arg04 = args[args_number-1], 1030 | 3 => config.arg03 = args[args_number-1], 1031 | 2 => config.arg02 = args[args_number-1], 1032 | 1 => config.arg01 = args[args_number-1], 1033 | _ => () 1034 | } 1035 | 1036 | args_number -= 1; 1037 | } 1038 | 1039 | let mut spoofy = get_cookie_value(); 1040 | if spoofy == 0 1041 | { 1042 | let current_rsp = get_current_rsp(); 1043 | spoofy = get_desirable_return_address(current_rsp, keep_start_function_frame); 1044 | } 1045 | 1046 | config.return_address = spoofy as *mut _; 1047 | let config: PVOID = std::mem::transmute(&config); 1048 | spoof_call(config) 1049 | } 1050 | } 1051 | 1052 | 1053 | // This function returns the main module's frame address in the stack. 1054 | // If it fails to do so, it will return the BaseThreadInitThunk's frame address instead. 1055 | fn get_desirable_return_address(current_rsp: usize, keep_start_function_frame: bool )-> usize 1056 | { 1057 | unsafe 1058 | { 1059 | let k32 = dinvoke_rs::dinvoke::get_module_base_address(&lc!("kernel32.dll")); 1060 | let mut addr: usize = 0; 1061 | let mut start_address = 1; 1062 | let mut end_address = 0; 1063 | let base_thread_init_thunk_start = dinvoke_rs::dinvoke::get_function_address(k32, &lc!("BaseThreadInitThunk")) as usize; 1064 | let base_thread_init_thunk_addresses = get_function_size(k32 as usize, base_thread_init_thunk_start); 1065 | 1066 | let base_thread_init_thunk_end = base_thread_init_thunk_addresses.1; 1067 | let thread_handle = GetCurrentThread(); 1068 | let thread_info_class = 9u32; 1069 | let thread_information = 0usize; 1070 | let thread_information: PVOID = std::mem::transmute(&thread_information); 1071 | let thread_info_len = 8u32; 1072 | let ret_len = 0u32; 1073 | let ret_len: *mut u32 = std::mem::transmute(&ret_len); 1074 | if keep_start_function_frame 1075 | { 1076 | // Obtain current thread's start address 1077 | let ret = dinvoke_rs::dinvoke::nt_query_information_thread(thread_handle, thread_info_class, thread_information, thread_info_len, ret_len); 1078 | if ret == 0 1079 | { 1080 | let thread_information = thread_information as *mut usize; 1081 | 1082 | let flags = 0x00000004; 1083 | let function_address: *const u8 = *thread_information as _; 1084 | let module_handle = 0usize; 1085 | let module_handle: *mut usize = std::mem::transmute(&module_handle); 1086 | 1087 | // Determine the module where the current thread's start function is located at. 1088 | let ret = dinvoke_rs::dinvoke::get_module_handle_ex_a(flags, function_address, module_handle); 1089 | 1090 | if ret 1091 | { 1092 | let base_address = *module_handle; 1093 | let function_addresses = get_function_size(base_address, function_address as _); 1094 | start_address = function_addresses.0; 1095 | end_address = function_addresses.1; 1096 | } 1097 | } 1098 | } 1099 | 1100 | let mut stack_iterator: *mut usize = current_rsp as *mut usize; 1101 | let mut found = false; 1102 | 1103 | while !found 1104 | { 1105 | // Check whether the value stored in this stack's address is located at current thread's start function or 1106 | // BaseThreadInitThunk. Otherwise, iterate to the next word in the stack and repeat the process. 1107 | if (*stack_iterator > start_address && *stack_iterator < end_address) || 1108 | (*stack_iterator > base_thread_init_thunk_start && *stack_iterator < base_thread_init_thunk_end) 1109 | { 1110 | addr = stack_iterator as usize; 1111 | let data = dinvoke_rs::dinvoke::tls_get_value(TLS_INDEX) as *mut usize; 1112 | *data = addr; 1113 | found = true; 1114 | } 1115 | 1116 | stack_iterator = stack_iterator.add(1); 1117 | } 1118 | 1119 | addr 1120 | } 1121 | } 1122 | 1123 | // TLS is used to store the main module's/BaseThreadInitThunk's frame top address in the stack. 1124 | // This allows to efficiently concatenate the spoofing process as many times as needed. 1125 | fn get_cookie_value() -> usize 1126 | { 1127 | unsafe 1128 | { 1129 | if TLS_INDEX == 0 1130 | { 1131 | let r = dinvoke_rs::dinvoke::tls_alloc(); 1132 | if r == TLS_OUT_OF_INDEXES { 1133 | return 0; 1134 | } 1135 | 1136 | TLS_INDEX = r; 1137 | } 1138 | 1139 | let value = dinvoke_rs::dinvoke::tls_get_value(TLS_INDEX) as *mut usize; 1140 | if value as usize == 0 1141 | { 1142 | let heap_region = dinvoke_rs::dinvoke::local_alloc(0x0040, 8); // 0x0040 = LPTR 1143 | 1144 | if heap_region != ptr::null_mut() { 1145 | let _ = dinvoke_rs::dinvoke::tls_set_value(TLS_INDEX, heap_region); 1146 | } 1147 | 1148 | return 0; 1149 | } 1150 | 1151 | *value 1152 | } 1153 | } 1154 | 1155 | // Use RuntimeFunction's data to get the size of a function. 1156 | fn get_function_size(base_address: usize, function_address: usize) -> (usize, usize) 1157 | { 1158 | unsafe 1159 | { 1160 | let exception_directory = get_runtime_table(base_address as _); 1161 | let mut rt = exception_directory.0; 1162 | if rt == ptr::null_mut() { 1163 | return (0,0); 1164 | } 1165 | 1166 | let items = exception_directory.1 / 12; 1167 | let mut count = 0; 1168 | while count < items 1169 | { 1170 | let function_start_address = (base_address + (*rt).begin_addr as usize) as *mut u8; 1171 | let function_end_address = (base_address + (*rt).end_addr as usize) as *mut u8; 1172 | if function_address >= function_start_address as usize && function_address < function_end_address as usize { 1173 | return (function_start_address as usize, function_end_address as usize); 1174 | } 1175 | 1176 | rt = rt.add(1); 1177 | count += 1; 1178 | } 1179 | 1180 | (0,0) 1181 | } 1182 | } 1183 | 1184 | // Don't call this function directly. Use indirect_syscall!() macro instead. 1185 | pub fn prepare_syscall(function_name: &str) -> (u32, usize) 1186 | { 1187 | 1188 | let ntdll = dinvoke_rs::dinvoke::get_module_base_address(&lc!("ntdll.dll")); 1189 | let eat = dinvoke_rs::dinvoke::get_ntdll_eat(ntdll); 1190 | let id = dinvoke_rs::dinvoke::get_syscall_id(&eat, function_name); 1191 | if id != u32::MAX 1192 | { 1193 | 1194 | let function_addr = dinvoke_rs::dinvoke::get_function_address(ntdll, function_name); 1195 | let syscall_addr: usize = dinvoke_rs::dinvoke::find_syscall_address(function_addr as usize); 1196 | if syscall_addr != 0 { 1197 | return (id as u32,syscall_addr); 1198 | } 1199 | 1200 | let max_range = eat.len(); 1201 | let mut rng: WyRand = WyRand::new(); 1202 | loop 1203 | { 1204 | let mut function = &"".to_string(); 1205 | for s in eat.values() 1206 | { 1207 | let index = rng.generate_range(0..max_range); 1208 | if index < max_range / 5 1209 | { 1210 | function = s; 1211 | break; 1212 | } 1213 | } 1214 | 1215 | let function_addr = dinvoke_rs::dinvoke::get_function_address(ntdll, function); 1216 | let syscall_addr: usize = dinvoke_rs::dinvoke::find_syscall_address(function_addr as usize); 1217 | if syscall_addr != 0 { 1218 | return (id as u32,syscall_addr); 1219 | } 1220 | } 1221 | } 1222 | 1223 | (u32::MAX,0) 1224 | } 1225 | 1226 | // Function used to find the JMP RBX and ADD RSP gadgets. 1227 | fn find_gadget(module: usize, gadget_frame_size: &mut i32, arg: i32, black_list: &mut Vec<(u32,u32)>) -> usize 1228 | { 1229 | unsafe 1230 | { 1231 | let exception_directory = get_runtime_table(module as *mut _); 1232 | let mut rt = exception_directory.0; 1233 | if rt == ptr::null_mut() { 1234 | return 0; 1235 | } 1236 | 1237 | let items = exception_directory.1 / 12; 1238 | let mut rng = WyRand::new(); 1239 | let rt_offset = rng.generate_range(0..(items/2)); 1240 | rt = rt.add(rt_offset as usize); 1241 | let mut count = rt_offset; 1242 | while count < items 1243 | { 1244 | let mut function_start_address = (module + (*rt).begin_addr as usize) as *mut u8; 1245 | let function_end_address = (module + (*rt).end_addr as usize) as *mut u8; 1246 | let item = ((*rt).begin_addr, (*rt).end_addr); 1247 | if black_list.contains(&item) 1248 | { 1249 | rt = rt.add(1); 1250 | count += 1; 1251 | continue; 1252 | } 1253 | 1254 | while (function_start_address as usize) < (function_end_address as usize) - 3 1255 | { 1256 | if (*(function_start_address as *mut u16) == JMP_RBX && arg == 0) || 1257 | (*(function_start_address as *mut u32) == ADD_RSP && *(function_start_address.add(4)) == 0xc3 && arg == 1) 1258 | { 1259 | *gadget_frame_size = get_frame_size_normal(module, *rt, false, &mut false); 1260 | if *gadget_frame_size == 0 1261 | { 1262 | function_start_address = function_start_address.add(1); 1263 | continue; 1264 | } 1265 | else 1266 | { 1267 | black_list.push(item); 1268 | return function_start_address as usize; 1269 | } 1270 | } 1271 | 1272 | function_start_address = function_start_address.add(1); 1273 | } 1274 | 1275 | rt = rt.add(1); 1276 | count += 1; 1277 | } 1278 | 1279 | 0 1280 | } 1281 | } 1282 | 1283 | // Find a function with a setfpreg unwind code. 1284 | fn find_setfpreg(module: usize, frame_size: &mut i32, black_list: &mut Vec<(u32,u32)>) -> usize 1285 | { 1286 | unsafe 1287 | { 1288 | let exception_directory = get_runtime_table(module as *mut _); 1289 | let mut rt = exception_directory.0; 1290 | if rt == ptr::null_mut() { 1291 | return 0; 1292 | } 1293 | 1294 | let items = exception_directory.1 / 12; 1295 | let mut rng = WyRand::new(); 1296 | let rt_offset = rng.generate_range(0..(items/2)); 1297 | rt = rt.add(rt_offset as usize); 1298 | let mut count = rt_offset; 1299 | while count < items 1300 | { 1301 | let runtime_function = *rt; 1302 | let mut found = false; 1303 | *frame_size = get_frame_size_with_setfpreg(module, runtime_function, &mut found); 1304 | if found && *frame_size != 0 1305 | { 1306 | let random_offset = generate_random_offset(module, runtime_function); 1307 | if random_offset != 0 1308 | { 1309 | let item = (runtime_function.begin_addr,runtime_function.end_addr); 1310 | black_list.push(item); 1311 | return (module + random_offset as usize) as _; 1312 | } 1313 | } 1314 | 1315 | rt = rt.add(1); 1316 | count += 1; 1317 | } 1318 | 1319 | 0 1320 | } 1321 | } 1322 | 1323 | // Find a function where RBP is pushed to the stack. 1324 | fn find_pushrbp(module: usize, frame_size: &mut i32, push_offset: &mut i32, black_list: &mut Vec<(u32,u32)>) -> usize 1325 | { 1326 | unsafe 1327 | { 1328 | let exception_directory = get_runtime_table(module as *mut _); 1329 | let mut rt = exception_directory.0; 1330 | if rt == ptr::null_mut() { 1331 | return 0; 1332 | } 1333 | 1334 | let items = exception_directory.1 / 12; 1335 | let mut rng = WyRand::new(); 1336 | let rt_offset = rng.generate_range(0..(items/2)); 1337 | rt = rt.add(rt_offset as usize); 1338 | let mut count = rt_offset; 1339 | while count < items 1340 | { 1341 | let runtime_function = *rt; 1342 | let item = (runtime_function.begin_addr,runtime_function.end_addr); 1343 | let mut found: bool = false; 1344 | *push_offset = 0; 1345 | *frame_size = 0i32; 1346 | get_frame_size_with_push_rbp(module, runtime_function, &mut found, push_offset, frame_size); 1347 | if found && *frame_size >= *push_offset && !black_list.contains(&item) 1348 | { 1349 | let random_offset = generate_random_offset(module, runtime_function); 1350 | if random_offset != 0 1351 | { 1352 | black_list.push(item); 1353 | return (module + random_offset as usize) as _; 1354 | } 1355 | } 1356 | 1357 | rt = rt.add(1); 1358 | count += 1; 1359 | } 1360 | 1361 | 0 1362 | } 1363 | } 1364 | 1365 | // Locate a call instruction in an arbitrary function and return the next instruction's address. 1366 | fn generate_random_offset(module: usize, runtime_function: RuntimeFunction) -> u32 1367 | { 1368 | let start_address = module + runtime_function.begin_addr as usize; 1369 | let end_address = module + runtime_function.end_addr as usize; 1370 | let pattern = vec![0x48,0xff,0x15]; // 0x48 0xff 0x15 00 00 00 00 = rex.W call QWORD PTR [rip+0x0] 1371 | let address = find_pattern(start_address, end_address, pattern); 1372 | 1373 | if address == -1 || address + 7 >= end_address as isize { 1374 | return 0; 1375 | } 1376 | 1377 | ((address + 7) - module as isize) as u32 1378 | } 1379 | 1380 | fn find_pattern(mut start_address: usize, end_address: usize, pattern: Vec) -> isize 1381 | { 1382 | unsafe 1383 | { 1384 | while start_address < (end_address - pattern.len()) 1385 | { 1386 | if *(start_address as *mut u8) == pattern[0] 1387 | { 1388 | let temp_iterator = start_address as *mut u8; 1389 | let mut found = true; 1390 | for i in 1..pattern.len() 1391 | { 1392 | if *temp_iterator.add(i) != pattern[i] 1393 | { 1394 | found = false; 1395 | break; 1396 | } 1397 | } 1398 | 1399 | if found { 1400 | return start_address as isize; 1401 | } 1402 | 1403 | } 1404 | 1405 | start_address += 1; 1406 | } 1407 | 1408 | -1 1409 | } 1410 | } 1411 | 1412 | fn get_frame_size_normal(module: usize, runtime_function: RuntimeFunction, ignore_rsp_and_bp: bool, base_pointer: &mut bool) -> i32 1413 | { 1414 | unsafe 1415 | { 1416 | let unwind_info = (module + runtime_function.unwind_addr as usize) as *mut u8; 1417 | let version_and_flags = (*unwind_info).to_ne_bytes().clone(); 1418 | let mut reader = BitReader::new(&version_and_flags); 1419 | 1420 | // We don't care about the version, we just need the flags to check if there is an Unwind Chain. 1421 | let flags = reader.read_u8(5).unwrap(); 1422 | let unwind_codes_count = *(unwind_info.add(2)); 1423 | 1424 | // We skip 4 bytes corresponding to Version + flags, Size of prolog, Count of unwind codes 1425 | // and Frame Register + Frame Register offset. 1426 | // This way we reach the Unwind codes array. 1427 | let mut unwind_code = (unwind_info.add(4)) as *mut u8; 1428 | let mut unwind_code_operation_code_info = unwind_code.add(1); 1429 | // This counter stores the size of the stack frame in bytes. 1430 | let mut frame_size = 0; 1431 | let mut index = 0; 1432 | while index < unwind_codes_count 1433 | { 1434 | let operation_code_and_info = (*unwind_code_operation_code_info).to_le_bytes().clone(); 1435 | let mut reader = BitReader::new(&operation_code_and_info); 1436 | 1437 | let operation_info = reader.read_u8(4).unwrap(); // operation info 1438 | let operation_code = reader.read_u8(4).unwrap(); // operation code 1439 | 1440 | match operation_code 1441 | { 1442 | 0 => 1443 | { 1444 | // UWOP_PUSH_NONVOL 1445 | 1446 | // operation_info == 4 -> RSP 1447 | if operation_code == 4 && !ignore_rsp_and_bp { 1448 | return 0; 1449 | } 1450 | 1451 | frame_size += 8; 1452 | } 1453 | 1 => 1454 | { 1455 | // UWOP_ALLOC_LARGE 1456 | if operation_info == 0 1457 | { 1458 | let size = *(unwind_code_operation_code_info.add(1) as *mut i16); 1459 | frame_size += size as i32 * 8; 1460 | 1461 | unwind_code = unwind_code.add(2); 1462 | index += 1; 1463 | 1464 | } 1465 | else if operation_info == 1 1466 | { 1467 | let size = *(unwind_code_operation_code_info.add(1) as *mut u16) as i32; 1468 | let size2 = (*(unwind_code_operation_code_info.add(3) as *mut u16) as i32) << 16; 1469 | frame_size += size + size2; 1470 | 1471 | unwind_code = unwind_code.add(4); 1472 | index += 2; 1473 | } 1474 | } 1475 | 2 => 1476 | { 1477 | // UWOP_ALLOC_SMALL 1478 | frame_size += (operation_info * 8 + 8) as i32; 1479 | } 1480 | 3 => 1481 | { 1482 | // UWOP_SET_FPREG // Dynamic alloc "does not change" frame's size 1483 | *base_pointer = true; // This is not used atm 1484 | if !ignore_rsp_and_bp { 1485 | return 0; // This is meant to prevent the use of return addresses corresponding to functions that set a base pointer 1486 | } 1487 | 1488 | } 1489 | 4 => 1490 | { 1491 | // UWOP_SAVE_NONVOL 1492 | // operation_info == 4 -> RSP 1493 | if operation_info == 4 && !ignore_rsp_and_bp { 1494 | return 0; 1495 | } 1496 | 1497 | unwind_code = unwind_code.add(2); 1498 | index += 1; 1499 | } 1500 | 5 => 1501 | { 1502 | // UWOP_SAVE_NONVOL_FAR 1503 | // operation_info == 4 -> RSP 1504 | if operation_info == 4 && !ignore_rsp_and_bp { 1505 | return 0; 1506 | } 1507 | 1508 | unwind_code = unwind_code.add(4); 1509 | index += 2; 1510 | } 1511 | 8 => 1512 | { 1513 | // UWOP_SAVE_XMM128 1514 | unwind_code = unwind_code.add(2); 1515 | index += 1; 1516 | } 1517 | 9 => 1518 | { 1519 | // UWOP_SAVE_XMM128_FAR 1520 | unwind_code = unwind_code.add(4); 1521 | index += 2; 1522 | } 1523 | 10 => 1524 | { 1525 | // UWOP_PUSH_MACH_FRAME 1526 | if operation_info == 0 { 1527 | frame_size += 64; // 0x40h 1528 | } else if operation_code == 1 { 1529 | frame_size += 72; // 0x48h 1530 | } 1531 | } 1532 | _=> {} 1533 | } 1534 | 1535 | unwind_code = unwind_code.add(2); 1536 | unwind_code_operation_code_info = unwind_code.add(1); 1537 | index += 1; 1538 | } 1539 | 1540 | // In case that the flag UNW_FLAG_CHAININFO is set, we recursively call this function. 1541 | if (flags & UNW_FLAG_CHAININFO) != 0 1542 | { 1543 | if unwind_codes_count % 2 != 0 { 1544 | unwind_code = unwind_code.add(2); 1545 | } 1546 | 1547 | let runtime_function: *mut RuntimeFunction = transmute(unwind_code); 1548 | let result = get_frame_size_normal(module, *runtime_function, ignore_rsp_and_bp, base_pointer); 1549 | 1550 | frame_size += result as i32; 1551 | } 1552 | 1553 | frame_size 1554 | } 1555 | } 1556 | 1557 | fn get_frame_size_with_setfpreg(module: usize, runtime_function: RuntimeFunction, found: &mut bool) -> i32 1558 | { 1559 | unsafe 1560 | { 1561 | let unwind_info = (module + runtime_function.unwind_addr as usize) as *mut u8; 1562 | let fp_info = unwind_info.add(3); 1563 | let frame_register_and_offset = (*fp_info).to_ne_bytes().clone(); // Little endian 1564 | 1565 | let mut reader = BitReader::new(&frame_register_and_offset); 1566 | let frame_register_offset = reader.read_u8(4).unwrap(); 1567 | let frame_register = reader.read_u8(4).unwrap(); 1568 | 1569 | let version_and_flags = (*unwind_info).to_ne_bytes().clone(); 1570 | let mut reader = BitReader::new(&version_and_flags); 1571 | 1572 | // We don't care about the version, we just need the flags to check if there is an Unwind Chain. 1573 | let flags = reader.read_u8(5).unwrap(); 1574 | 1575 | let unwind_codes_count = *(unwind_info.add(2)); 1576 | 1577 | // We skip 4 bytes corresponding to Version + flags, Size of prolog, Count of unwind codes 1578 | // and Frame Register + Frame Register offset. 1579 | // This way we reach the Unwind codes array. 1580 | let mut unwind_code = (unwind_info.add(4)) as *mut u8; 1581 | let mut unwind_code_operation_code_info = unwind_code.add(1); 1582 | // This counter stores the size of the stack frame in bytes. 1583 | let mut frame_size = 0; 1584 | let mut index = 0; 1585 | while index < unwind_codes_count 1586 | { 1587 | let operation_code_and_info = (*unwind_code_operation_code_info).to_ne_bytes().clone(); 1588 | let mut reader = BitReader::new(&operation_code_and_info); 1589 | 1590 | let operation_info = reader.read_u8(4).unwrap(); // operation info 1591 | let operation_code = reader.read_u8(4).unwrap(); // operation code 1592 | 1593 | match operation_code 1594 | { 1595 | 0 => 1596 | { 1597 | // UWOP_PUSH_NONVOL 1598 | 1599 | if operation_info == 4 && !*found { 1600 | return 0; 1601 | } 1602 | 1603 | frame_size += 8; 1604 | } 1605 | 1 => 1606 | { 1607 | // UWOP_ALLOC_LARGE 1608 | if operation_info == 0 1609 | { 1610 | let size = *(unwind_code_operation_code_info.add(1) as *mut i16); 1611 | frame_size += size as i32 * 8; 1612 | 1613 | unwind_code = unwind_code.add(2); 1614 | index += 1; 1615 | 1616 | } 1617 | else if operation_info == 1 1618 | { 1619 | let size = *(unwind_code_operation_code_info.add(1) as *mut u16) as i32; 1620 | let size2 = (*(unwind_code_operation_code_info.add(3) as *mut u16) as i32) << 16; 1621 | frame_size += size + size2; 1622 | 1623 | unwind_code = unwind_code.add(4); 1624 | index += 2; 1625 | } 1626 | } 1627 | 2 => 1628 | { 1629 | // UWOP_ALLOC_SMALL 1630 | frame_size += (operation_info * 8 + 8) as i32; 1631 | } 1632 | 3 => 1633 | { 1634 | // UWOP_SET_FPREG 1635 | if (flags & UNW_FLAG_EHANDLER) != 0 && (flags & UNW_FLAG_CHAININFO) != 0 1636 | { 1637 | *found = false; 1638 | return 0; 1639 | } 1640 | 1641 | // This checks if the register used as FP is RBP 1642 | if frame_register != 5 1643 | { 1644 | *found = false; 1645 | return 0; 1646 | } 1647 | 1648 | *found = true; 1649 | let offset = 16 * frame_register_offset; 1650 | frame_size -= offset as i32; 1651 | } 1652 | 4 => 1653 | { 1654 | // UWOP_SAVE_NONVOL 1655 | unwind_code = unwind_code.add(2); 1656 | index += 1; 1657 | } 1658 | 5 => 1659 | { 1660 | // UWOP_SAVE_NONVOL_FAR 1661 | unwind_code = unwind_code.add(4); 1662 | index += 2; 1663 | } 1664 | 8 => 1665 | { 1666 | // UWOP_SAVE_XMM128 1667 | unwind_code = unwind_code.add(2); 1668 | index += 1; 1669 | } 1670 | 9 => 1671 | { 1672 | // UWOP_SAVE_XMM128_FAR 1673 | unwind_code = unwind_code.add(4); 1674 | index += 2; 1675 | } 1676 | 10 => 1677 | { 1678 | // UWOP_PUSH_MACH_FRAME 1679 | if operation_info == 0 { 1680 | frame_size += 64; // 0x40h 1681 | } else if operation_code == 1 { 1682 | frame_size += 72; // 0x48h 1683 | } 1684 | } 1685 | _=> {} 1686 | } 1687 | 1688 | unwind_code = unwind_code.add(2); 1689 | unwind_code_operation_code_info = unwind_code.add(1); 1690 | index += 1; 1691 | } 1692 | 1693 | // In case that the flag UNW_FLAG_CHAININFO is set, we recursively call this function. 1694 | if (flags & UNW_FLAG_CHAININFO) != 0 1695 | { 1696 | if unwind_codes_count % 2 != 0 { 1697 | unwind_code = unwind_code.add(2); 1698 | } 1699 | 1700 | let runtime_function: *mut RuntimeFunction = transmute(unwind_code); 1701 | let result = get_frame_size_with_setfpreg(module, *runtime_function, found); 1702 | 1703 | frame_size += result as i32 ; 1704 | } 1705 | 1706 | frame_size 1707 | } 1708 | } 1709 | 1710 | fn get_frame_size_with_push_rbp(module: usize, runtime_function: RuntimeFunction, found: &mut bool, push_offset: &mut i32, frame_size: &mut i32) 1711 | { 1712 | unsafe 1713 | { 1714 | let unwind_info = (module + runtime_function.unwind_addr as usize) as *mut u8; 1715 | let version_and_flags = (*unwind_info).to_ne_bytes().clone(); 1716 | let mut reader = BitReader::new(&version_and_flags); 1717 | 1718 | // We don't care about the version, we just need the flags to check if there is an Unwind Chain. 1719 | let flags = reader.read_u8(5).unwrap(); 1720 | let unwind_codes_count = *(unwind_info.add(2)); 1721 | 1722 | // We skip 4 bytes corresponding to Version + flags, Size of prolog, Count of unwind codes 1723 | // and Frame Register + Frame Register offset. 1724 | // This way we reach the Unwind codes array. 1725 | let mut unwind_code = (unwind_info.add(4)) as *mut u8; 1726 | let mut unwind_code_operation_code_info = unwind_code.add(1); 1727 | 1728 | let mut index = 0; 1729 | while index < unwind_codes_count 1730 | { 1731 | let operation_code_and_info = (*unwind_code_operation_code_info).to_ne_bytes().clone(); 1732 | let mut reader = BitReader::new(&operation_code_and_info); 1733 | 1734 | let operation_info = reader.read_u8(4).unwrap(); // operation info 1735 | let operation_code = reader.read_u8(4).unwrap(); // operation code 1736 | 1737 | match operation_code 1738 | { 1739 | 0 => 1740 | { 1741 | // UWOP_PUSH_NONVOL 1742 | 1743 | // operation_info == 4 -> RSP 1744 | if operation_code == 4 1745 | { 1746 | *found = false; 1747 | *frame_size = 0; 1748 | return; 1749 | } 1750 | 1751 | // operation_info == 5 -> RBP 1752 | if operation_info == 5 1753 | { 1754 | if *found 1755 | { 1756 | *found = false; 1757 | *frame_size = 0; 1758 | return; 1759 | } 1760 | 1761 | *push_offset = *frame_size; 1762 | *found = true; 1763 | } 1764 | 1765 | *frame_size += 8; 1766 | } 1767 | 1 => 1768 | { 1769 | // UWOP_ALLOC_LARGE 1770 | if operation_info == 0 1771 | { 1772 | let size = *(unwind_code_operation_code_info.add(1) as *mut i16); 1773 | *frame_size += size as i32 * 8; 1774 | 1775 | unwind_code = unwind_code.add(2); 1776 | index += 1; 1777 | 1778 | } 1779 | else if operation_info == 1 1780 | { 1781 | let size = *(unwind_code_operation_code_info.add(1) as *mut u16) as i32; 1782 | let size2 = (*(unwind_code_operation_code_info.add(3) as *mut u16) as i32) << 16; 1783 | *frame_size += size + size2; 1784 | 1785 | unwind_code = unwind_code.add(4); 1786 | index += 2; 1787 | } 1788 | } 1789 | 2 => 1790 | { 1791 | // UWOP_ALLOC_SMALL 1792 | *frame_size += (operation_info * 8 + 8) as i32; 1793 | } 1794 | 3 => 1795 | { 1796 | // UWOP_SET_FPREG 1797 | *found = false; 1798 | *frame_size = 0; 1799 | return; 1800 | } 1801 | 4 => 1802 | { 1803 | // UWOP_SAVE_NONVOL 1804 | 1805 | if operation_info == 4 1806 | { 1807 | *found = false; 1808 | *frame_size = 0; 1809 | return; 1810 | } 1811 | 1812 | // operation_info == 5 -> RBP 1813 | if operation_info == 5 1814 | { 1815 | if *found 1816 | { 1817 | *found = false; 1818 | *frame_size = 0; 1819 | return; 1820 | } 1821 | 1822 | // The scaled-by-8 offset is stored in the next unwind code, which is a short (2 bytes) 1823 | let offset = *(unwind_code_operation_code_info.add(1) as *mut u16) as i32 * 8; 1824 | *push_offset = *frame_size + offset; 1825 | *found = true; 1826 | 1827 | } 1828 | 1829 | unwind_code = unwind_code.add(2); 1830 | index += 1; 1831 | 1832 | } 1833 | 5 => 1834 | { 1835 | // UWOP_SAVE_NONVOL_FAR 1836 | 1837 | if operation_info == 4 1838 | { 1839 | *found = false; 1840 | *frame_size = 0; 1841 | return; 1842 | } 1843 | 1844 | // operation_info == 5 -> RBP 1845 | if operation_info == 5 1846 | { 1847 | if *found 1848 | { 1849 | *found = false; 1850 | *frame_size = 0; 1851 | return; 1852 | } 1853 | 1854 | let offset1 = *(unwind_code_operation_code_info.add(1) as *mut u16) as i32; 1855 | let offset2 = (*(unwind_code_operation_code_info.add(3) as *mut u16) as i32) << 16; 1856 | let offset = offset1 + offset2; 1857 | *push_offset = *frame_size + offset; 1858 | *found = true; 1859 | 1860 | } 1861 | 1862 | unwind_code = unwind_code.add(4); 1863 | index += 2; 1864 | 1865 | } 1866 | 8 => 1867 | { 1868 | // UWOP_SAVE_XMM128 1869 | unwind_code = unwind_code.add(2); 1870 | index += 1; 1871 | } 1872 | 9 => 1873 | { 1874 | // UWOP_SAVE_XMM128_FAR 1875 | unwind_code = unwind_code.add(4); 1876 | index += 2; 1877 | } 1878 | 10 => 1879 | { 1880 | // UWOP_PUSH_MACH_FRAME 1881 | if operation_info == 0 { 1882 | *frame_size += 64; // 0x40 1883 | } else if operation_code == 1 { 1884 | *frame_size += 72; // 0x48 1885 | } 1886 | } 1887 | _=> {} 1888 | } 1889 | 1890 | unwind_code = unwind_code.add(2); 1891 | unwind_code_operation_code_info = unwind_code.add(1); 1892 | index += 1; 1893 | } 1894 | 1895 | // In case that the flag UNW_FLAG_CHAININFO is set, we recursively call this function. 1896 | if (flags & UNW_FLAG_CHAININFO) != 0 1897 | { 1898 | if unwind_codes_count % 2 != 0 { 1899 | unwind_code = unwind_code.add(2); 1900 | } 1901 | 1902 | let runtime_function: *mut RuntimeFunction = transmute(unwind_code); 1903 | get_frame_size_with_push_rbp(module, *runtime_function, found, push_offset, frame_size); 1904 | 1905 | } 1906 | } 1907 | } 1908 | 1909 | /// Returns a pair containing a pointer to the Exception data of an arbitrary module and the size of the 1910 | /// corresponding PE section (.pdata). In case that it fails to retrieve this information, it returns 1911 | /// null values (ptr::null_mut(), 0). 1912 | fn get_runtime_table(image_ptr: *mut c_void) -> (*mut dinvoke_rs::data::RuntimeFunction, u32) 1913 | { 1914 | unsafe 1915 | { 1916 | let module_metadata = dinvoke_rs::manualmap::get_pe_metadata(image_ptr as *const u8, false); 1917 | if module_metadata.is_err() { 1918 | return (ptr::null_mut(), 0); 1919 | } 1920 | 1921 | let metadata = module_metadata.unwrap(); 1922 | 1923 | let mut size: u32 = 0; 1924 | let mut runtime: *mut dinvoke_rs::data::RuntimeFunction = ptr::null_mut(); 1925 | for section in &metadata.sections 1926 | { 1927 | let s = std::str::from_utf8(§ion.Name).unwrap(); 1928 | if s.contains(".pdata") 1929 | { 1930 | let base = image_ptr as isize; 1931 | runtime = std::mem::transmute(base + section.VirtualAddress as isize); 1932 | size = section.SizeOfRawData; 1933 | return (runtime, size); 1934 | } 1935 | } 1936 | 1937 | (runtime, size) 1938 | } 1939 | } --------------------------------------------------------------------------------