├── 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 | 
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 | 
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 | 
185 | 
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 |
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 |
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 | }
--------------------------------------------------------------------------------