Skip to main content

spoof/
unwind.rs

1//! Walk UNWIND_INFO records to derive stack sizes and RBP offsets. Three
2//! walkers (rbp_offset, stack_frame, ignoring_set_fpreg) verified against
3//! live ntdll, kernelbase, and kernel32 prologues.
4
5use core::ffi::c_void;
6
7use crate::types::{
8    ImageRuntimeFunction, Registers, UnwindCode, UnwindInfo, UnwindOpCode::*,
9    UnwindOpCode, UNW_FLAG_CHAININFO, UNW_FLAG_EHANDLER,
10};
11
12/// Determine RBP push location and total stack size. Returns None if the
13/// frame layout isn't spoof-compatible (RSP saved as nonvol, RBP pushed
14/// twice, etc.).
15pub unsafe fn rbp_offset(
16    module: *mut c_void,
17    runtime: &ImageRuntimeFunction,
18) -> Option<(u32, u32)> {
19    let unwind_info = (module as usize + runtime.UnwindData as usize) as *mut UnwindInfo;
20    let unwind_code = (unwind_info as *mut u8).add(4) as *mut UnwindCode;
21    let flag = (*unwind_info).VersionFlags.Flags();
22
23    let mut i = 0usize;
24    let mut total_stack = 0u32;
25    let mut rbp_pushed = false;
26    let mut stack_offset = 0u32;
27
28    while i < (*unwind_info).CountOfCodes as usize {
29        let unwind_code = unwind_code.add(i);
30        let op_info = (*unwind_code).Anonymous.OpInfo() as usize;
31        let unwind_op = (*unwind_code).Anonymous.UnwindOp();
32
33        match UnwindOpCode::try_from(unwind_op) {
34            Ok(UWOP_PUSH_NONVOL) => {
35                if Registers::Rsp == op_info {
36                    return None;
37                }
38                if Registers::Rbp == op_info {
39                    if rbp_pushed {
40                        return None;
41                    }
42                    rbp_pushed = true;
43                    stack_offset = total_stack;
44                }
45                total_stack += 8;
46                i += 1;
47            }
48            Ok(UWOP_ALLOC_LARGE) => {
49                if (*unwind_code).Anonymous.OpInfo() == 0 {
50                    let frame_offset = ((*unwind_code.add(1)).FrameOffset as i32) * 8;
51                    total_stack += frame_offset as u32;
52                    i += 2;
53                } else {
54                    let frame_offset = *(unwind_code.add(1) as *mut i32);
55                    total_stack += frame_offset as u32;
56                    i += 3;
57                }
58            }
59            Ok(UWOP_ALLOC_SMALL) => {
60                total_stack += ((op_info + 1) * 8) as u32;
61                i += 1;
62            }
63            Ok(UWOP_SAVE_NONVOL) => {
64                if Registers::Rsp == op_info {
65                    return None;
66                }
67                if Registers::Rbp == op_info {
68                    if rbp_pushed {
69                        return None;
70                    }
71                    let offset = (*unwind_code.add(1)).FrameOffset * 8;
72                    stack_offset = total_stack + offset as u32;
73                    rbp_pushed = true;
74                }
75                i += 2;
76            }
77            Ok(UWOP_SAVE_NONVOL_BIG) => {
78                if Registers::Rsp == op_info {
79                    return None;
80                }
81                if Registers::Rbp == op_info {
82                    if rbp_pushed {
83                        return None;
84                    }
85                    let offset = *(unwind_code.add(1) as *mut u32);
86                    stack_offset = total_stack + offset;
87                    rbp_pushed = true;
88                }
89                i += 3;
90            }
91            Ok(UWOP_SET_FPREG) => return None,
92            Ok(UWOP_SAVE_XMM128) => i += 2,
93            Ok(UWOP_SAVE_XMM128BIG) => i += 3,
94            Ok(UWOP_EPILOG) | Ok(UWOP_SPARE_CODE) => i += 1,
95            Ok(UWOP_PUSH_MACH_FRAME) => {
96                total_stack += if op_info == 0 { 0x40 } else { 0x48 };
97                i += 1;
98            }
99            _ => return None,
100        }
101    }
102
103    if (flag & UNW_FLAG_CHAININFO) != 0 {
104        let count = (*unwind_info).CountOfCodes as usize;
105        let index = if count & 1 == 1 { count + 1 } else { count };
106        let runtime = unwind_code.add(index) as *const ImageRuntimeFunction;
107        if let Some((_, child_total)) = rbp_offset(module, &*runtime) {
108            total_stack += child_total;
109        } else {
110            return None;
111        }
112    }
113
114    Some((stack_offset, total_stack))
115}
116
117/// Compute total stack size, rejecting any frame that uses RBP/RSP as a
118/// non-volatile save target. Used when picking the first decoy prologue.
119pub unsafe fn stack_frame(
120    module: *mut c_void,
121    runtime: &ImageRuntimeFunction,
122) -> Option<(bool, u32)> {
123    let unwind_info = (module as usize + runtime.UnwindData as usize) as *mut UnwindInfo;
124    let unwind_code = (unwind_info as *mut u8).add(4) as *mut UnwindCode;
125    let flag = (*unwind_info).VersionFlags.Flags();
126
127    let mut i = 0usize;
128    let mut set_fpreg_hit = false;
129    let mut total_stack = 0i32;
130
131    while i < (*unwind_info).CountOfCodes as usize {
132        let unwind_code = unwind_code.add(i);
133        let op_info = (*unwind_code).Anonymous.OpInfo() as usize;
134        let unwind_op = (*unwind_code).Anonymous.UnwindOp();
135
136        match UnwindOpCode::try_from(unwind_op) {
137            Ok(UWOP_PUSH_NONVOL) => {
138                if Registers::Rsp == op_info && !set_fpreg_hit {
139                    return None;
140                }
141                total_stack += 8;
142                i += 1;
143            }
144            Ok(UWOP_ALLOC_SMALL) => {
145                total_stack += ((op_info + 1) * 8) as i32;
146                i += 1;
147            }
148            Ok(UWOP_ALLOC_LARGE) => {
149                if (*unwind_code).Anonymous.OpInfo() == 0 {
150                    let frame_offset = ((*unwind_code.add(1)).FrameOffset as i32) * 8;
151                    total_stack += frame_offset;
152                    i += 2;
153                } else {
154                    let frame_offset = *(unwind_code.add(1) as *mut i32);
155                    total_stack += frame_offset;
156                    i += 3;
157                }
158            }
159            Ok(UWOP_SAVE_NONVOL) => {
160                if Registers::Rsp == op_info || Registers::Rbp == op_info {
161                    return None;
162                }
163                i += 2;
164            }
165            Ok(UWOP_SAVE_NONVOL_BIG) => {
166                if Registers::Rsp == op_info || Registers::Rbp == op_info {
167                    return None;
168                }
169                i += 3;
170            }
171            Ok(UWOP_SAVE_XMM128) => i += 2,
172            Ok(UWOP_SAVE_XMM128BIG) => i += 3,
173            Ok(UWOP_SET_FPREG) => {
174                if (flag & UNW_FLAG_EHANDLER) != 0 && (flag & UNW_FLAG_CHAININFO) != 0 {
175                    return None;
176                }
177                if (*unwind_info).FrameInfo.FrameRegister() != Registers::Rbp as u8 {
178                    return None;
179                }
180                set_fpreg_hit = true;
181                let offset = ((*unwind_info).FrameInfo.FrameOffset() as i32) << 4;
182                total_stack -= offset;
183                i += 1;
184            }
185            Ok(UWOP_EPILOG) | Ok(UWOP_SPARE_CODE) => i += 1,
186            Ok(UWOP_PUSH_MACH_FRAME) => {
187                total_stack += if op_info == 0 { 0x40 } else { 0x48 };
188                i += 1;
189            }
190            _ => return None,
191        }
192    }
193
194    if (flag & UNW_FLAG_CHAININFO) != 0 {
195        let count = (*unwind_info).CountOfCodes as usize;
196        let index = if count & 1 == 1 { count + 1 } else { count };
197        let runtime = unwind_code.add(index) as *const ImageRuntimeFunction;
198        if let Some((chained_fpreg_hit, chained_stack)) = stack_frame(module, &*runtime) {
199            total_stack += chained_stack as i32;
200            set_fpreg_hit |= chained_fpreg_hit;
201        } else {
202            return None;
203        }
204    }
205
206    Some((set_fpreg_hit, total_stack as u32))
207}
208
209/// Total stack size, treating set_fpreg as a no-op. Used for sizing the
210/// frames we splice in (RtlUserThreadStart, BaseThreadInitThunk, gadget
211/// frames).
212pub unsafe fn ignoring_set_fpreg(
213    module: *mut c_void,
214    runtime: &ImageRuntimeFunction,
215) -> Option<u32> {
216    let unwind_info = (module as usize + runtime.UnwindData as usize) as *mut UnwindInfo;
217    let unwind_code = (unwind_info as *mut u8).add(4) as *mut UnwindCode;
218    let flag = (*unwind_info).VersionFlags.Flags();
219
220    let mut i = 0usize;
221    let mut total_stack = 0u32;
222
223    while i < (*unwind_info).CountOfCodes as usize {
224        let unwind_code = unwind_code.add(i);
225        let op_info = (*unwind_code).Anonymous.OpInfo() as usize;
226        let unwind_op = (*unwind_code).Anonymous.UnwindOp();
227
228        match UnwindOpCode::try_from(unwind_op) {
229            Ok(UWOP_PUSH_NONVOL) => {
230                if Registers::Rsp == op_info {
231                    return None;
232                }
233                total_stack += 8;
234                i += 1;
235            }
236            Ok(UWOP_ALLOC_SMALL) => {
237                total_stack += ((op_info + 1) * 8) as u32;
238                i += 1;
239            }
240            Ok(UWOP_ALLOC_LARGE) => {
241                if (*unwind_code).Anonymous.OpInfo() == 0 {
242                    let frame_offset = ((*unwind_code.add(1)).FrameOffset as i32) * 8;
243                    total_stack += frame_offset as u32;
244                    i += 2;
245                } else {
246                    let frame_offset = *(unwind_code.add(1) as *mut i32);
247                    total_stack += frame_offset as u32;
248                    i += 3;
249                }
250            }
251            Ok(UWOP_SAVE_NONVOL) => {
252                if Registers::Rsp == op_info {
253                    return None;
254                }
255                i += 2;
256            }
257            Ok(UWOP_SAVE_NONVOL_BIG) => {
258                if Registers::Rsp == op_info {
259                    return None;
260                }
261                i += 3;
262            }
263            Ok(UWOP_SAVE_XMM128) => i += 2,
264            Ok(UWOP_SAVE_XMM128BIG) => i += 3,
265            Ok(UWOP_SET_FPREG) => i += 1,
266            Ok(UWOP_EPILOG) | Ok(UWOP_SPARE_CODE) => i += 1,
267            Ok(UWOP_PUSH_MACH_FRAME) => {
268                total_stack += if op_info == 0 { 0x40 } else { 0x48 };
269                i += 1;
270            }
271            _ => return None,
272        }
273    }
274
275    if (flag & UNW_FLAG_CHAININFO) != 0 {
276        let count = (*unwind_info).CountOfCodes as usize;
277        let index = if count & 1 == 1 { count + 1 } else { count };
278        let runtime = unwind_code.add(index) as *const ImageRuntimeFunction;
279        if let Some(chained_stack) = ignoring_set_fpreg(module, &*runtime) {
280            total_stack += chained_stack;
281        } else {
282            return None;
283        }
284    }
285
286    Some(total_stack)
287}