1use crate::Boolean;
2use crate::connections::Connection;
3use crate::drcore::log;
4use crate::drwrap::Retval;
5use crate::socket::{SocketData, WSABufRaw, WSAOverlappedRaw};
6use crate::utils::FromBuf;
7use crate::utils::socketaddr_from_socket_id;
8use crate::{cli, connections, drcore, drwrap, ffi, instrument, pipe, trajectory};
9use enum_iterator::Sequence;
10use navigator::callstack::CallStack;
11use navigator::netgraph::core::{
12 Attempt, ConnectAttempt, ConnectReact, React, RecvAttempt, RecvReact, SendAttempt, SendReact,
13};
14use navigator::netgraph::trajectory::GraphStep;
15use navigator::pipe_event::PipeEvent;
16use navigator::socket::{HalfDuplexExchange, HalfDuplexResponse, RecvBuffer};
17use navigator::trajectory::PlanningStep;
18use std::sync::Arc;
19use std::{net::SocketAddr, os::raw::c_void};
20
21const SOCKET_ERROR: i32 = -1;
22const WSAEHOSTUNREACH: i32 = 10065;
23const WSA_IO_PENDING: i32 = 997;
24const WSAECONNRESET: i32 = 10054;
25const SIO_GET_EXTENSION_FUNCTION_POINTER: u32 = 0xC8000006;
26const WSAID_CONNECTEX: [u8; 16] = [
28 0xb9, 0x07, 0xa2, 0x25, 0xf3, 0xdd, 0x60, 0x46, 0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e,
29];
30
31#[derive(Debug, PartialEq, Sequence)]
32pub enum Symbols {
33 WSAioctl,
34 Connect,
35 Send,
36 WSASend,
37 Recv,
38 WSARecv,
39}
40
41impl Symbols {
42 pub fn pre_fn(
43 &self,
44 ) -> unsafe extern "C" fn(wrapctx: *mut c_void, _user_data: *mut *mut c_void) {
45 match self {
46 Symbols::WSAioctl => pre_wsaioctl,
47 Symbols::Connect => pre_connect,
48 Symbols::Send => pre_send,
49 Symbols::WSASend => pre_wsasend,
50 Symbols::Recv => pre_recv,
51 Symbols::WSARecv => pre_wsarecv,
52 }
53 }
54 pub fn post_fn(
55 &self,
56 ) -> Option<unsafe extern "C" fn(wrapctx: *mut c_void, _user_data: *mut c_void)> {
57 match self {
58 Symbols::WSAioctl => Some(post_wsaioctl),
59 _ => None,
60 }
61 }
62 pub fn to_str(&self) -> &str {
63 match self {
64 Symbols::WSAioctl => "wsaioctl",
65 Symbols::Connect => "connect",
66 Symbols::Send => "send",
67 Symbols::WSASend => "wsasend",
68 Symbols::Recv => "recv",
69 Symbols::WSARecv => "wsarecv",
70 }
71 }
72}
73
74pub unsafe extern "C" fn pre_wsaioctl(wrapcxt: *mut c_void, user_data: *mut *mut c_void) {
76 log("[WSAIoctl] Pre-callback called");
77
78 let io_control_code = unsafe { ffi::drwrap_get_arg(wrapcxt, 1) as u32 };
80 let lpv_in = unsafe { ffi::drwrap_get_arg(wrapcxt, 2) as *const u8 };
81 let cb_in = unsafe { ffi::drwrap_get_arg(wrapcxt, 3) as u32 };
82 let lpv_out = unsafe { ffi::drwrap_get_arg(wrapcxt, 4) as *mut usize };
83
84 log(&format!("[WSAIoctl] Control code: 0x{:X}", io_control_code));
85
86 if io_control_code == SIO_GET_EXTENSION_FUNCTION_POINTER {
88 if !lpv_in.is_null() && cb_in >= 16 {
89 let guid_slice = unsafe { std::slice::from_raw_parts(lpv_in, 16) };
90
91 if guid_slice == &WSAID_CONNECTEX {
92 log("[WSAIoctl] Detected ConnectEx request - will wrap in post-callback");
93
94 unsafe {
96 *user_data = lpv_out as *mut c_void;
97 }
98 return;
100 }
101 }
102 }
103
104 log("[WSAIoctl] Not a ConnectEx request - letting WSAIoctl proceed");
106}
107
108pub unsafe extern "C" fn post_wsaioctl(_wrapcxt: *mut c_void, user_data: *mut c_void) {
110 if user_data.is_null() {
111 return;
113 }
114
115 log("[WSAIoctl] Post-callback called");
116
117 let lpv_out = user_data as *mut usize;
119
120 if !lpv_out.is_null() {
121 let real_connectex_ptr = unsafe { *lpv_out };
123 log(&format!(
124 "[WSAIoctl] Real ConnectEx pointer: 0x{:X}",
125 real_connectex_ptr
126 ));
127
128 if real_connectex_ptr != 0 {
129 unsafe {
131 match ffi::drwrap_wrap(real_connectex_ptr as *mut u8, Some(pre_connectex), None)
132 .as_bool()
133 {
134 true => log(&format!(
135 "[WSAIoctl] Successfully wrapped real ConnectEx @ 0x{:X}",
136 real_connectex_ptr
137 )),
138 false => log(&format!(
139 "[WSAIoctl] FAILED to wrap real ConnectEx @ 0x{:X}",
140 real_connectex_ptr
141 )),
142 }
143 }
144 }
145 }
146}
147
148#[unsafe(no_mangle)]
149pub unsafe extern "C" fn pre_connectex(wrapctx: *mut c_void, _user_data: *mut *mut c_void) {
150 unsafe {
151 let _send_buffer = drwrap::get_arg(wrapctx, 3);
152 let send_data_length = drwrap::get_arg(wrapctx, 4) as u32;
153
154 if send_data_length != 0 {
155 log(
156 "[connectex] send_data_length is not 0! Implementation required to parse the send payload accompanying this connect",
157 );
158 unimplemented!(
159 "[connectex] send_data_length is not 0! Implementation required to parse the send payload accompanying this connect"
160 );
161 }
162
163 let stdcall_args_size = if cfg!(target_pointer_width = "32") {
165 28
167 } else {
168 0
169 };
170
171 let (socket_id, socket_addr) = parse_socket(wrapctx, "connectex");
172 let graph_step = parse_connect_graph_step(socket_addr, "connectex");
173 pipe::send(&PipeEvent::NetGraph(graph_step.clone()));
174
175 if let GraphStep::Connect(_, ConnectReact { success: true }) = graph_step {
177 let socket_data = SocketData::ServerManaged;
178 connections::insert(socket_id, socket_addr, socket_data);
179 } else {
181 let retval = Retval::Int(false as i32);
182 let error_code = WSAEHOSTUNREACH;
183 unimplemented!("TODO: handle connection rejection");
184 log(&format!(
185 "[connectex] Stored error code {} in TLS for WSAGetLastError hook",
186 error_code
187 ));
188 skip_call_helper(wrapctx, retval, "connectex", stdcall_args_size);
189 }
190 }
191}
192
193#[unsafe(no_mangle)]
195pub unsafe extern "C" fn pre_connect(wrapctx: *mut c_void, _user_data: *mut *mut c_void) {
196 unsafe {
197 let stdcall_args_size = if cfg!(target_pointer_width = "32") {
199 12
201 } else {
202 0
203 };
204
205 let (socket_id, socket_addr) = parse_socket(wrapctx, "connect");
206 let graph_step = parse_connect_graph_step(socket_addr, "connect");
207 pipe::send(&PipeEvent::NetGraph(graph_step.clone()));
208
209 if let GraphStep::Connect(_, ConnectReact { success: true }) = graph_step {
211 if cli::get_args()
212 .expect("Args parsed in Libinject init")
213 .server_managed
214 {
215 let socket_data = SocketData::ServerManaged;
216 connections::insert(socket_id, socket_addr, socket_data);
217 } else {
218 let socket_data = SocketData::Sync(RecvBuffer::new());
219 connections::insert(socket_id, socket_addr, socket_data);
220 let retval = Retval::Int(0);
221 skip_call_helper(wrapctx, retval, "connect", stdcall_args_size);
222 };
223 } else {
224 let retval = Retval::Int(SOCKET_ERROR);
225 skip_call_helper(wrapctx, retval, "connect", stdcall_args_size);
226 }
227 }
228}
229
230unsafe fn connection_of_socket(socket_id: usize, wrapper_name: &str) -> Arc<Connection> {
231 unsafe {
232 connections::get(socket_id).unwrap_or_else(|| {
233 log(&format!("[{wrapper_name}] Binary tried to call {wrapper_name} with a socket that we have not recorded a connection for! Trying to parse the socket address from the socket_id..."));
234 match socketaddr_from_socket_id(socket_id) {
235 Ok(socket_addr) => {
236 log(&format!("[{wrapper_name}] Successfully parsed socket address from socket_id. Recording this connection."));
237 connections::insert(socket_id, socket_addr, SocketData::ServerManaged);
238 connections::get(socket_id).unwrap()
239 },
240 Err(e) => {
241 log(&format!("[{wrapper_name} Failed parsing SocketAddr from socket_id (opaque OS handle): {e}"));
242 log(&format!("[{wrapper_name}] If there is only one connection, we will assume the socket_id has changed but the IP address remains the same."));
243 connections::exactly_one().expect("[{wrapper_name}] There is not exactly one connection")
244 }
245 }
246 })
247 }
248}
249
250#[unsafe(no_mangle)]
251pub unsafe extern "C" fn pre_wsasend(wrapctx: *mut c_void, _user_data: *mut *mut c_void) {
252 unsafe {
253 log("[WSASend] Pre-callback called *********");
254 let stdcall_args_size = 28;
255
256 let socket_id = drwrap::get_arg(wrapctx, 0) as usize;
257 let connection = connection_of_socket(socket_id, "wsasend");
258
259 let buffers_ptr = drwrap::get_arg(wrapctx, 1) as *mut WSABufRaw;
260 let buffer_count = drwrap::get_arg(wrapctx, 2) as usize;
261 log(&format!("[WSASend] buffer count: {buffer_count}"));
262
263 if buffers_ptr.is_null() {
264 panic!("[WSASend] buffer_ptr is null!");
265 }
266
267 let overlapped_ptr = drwrap::get_arg(wrapctx, 5) as *mut WSAOverlappedRaw;
268 let completion_routine_ptr = drwrap::get_arg(wrapctx, 6);
269 log(&format!(
270 "[WSASend] overlapper ptr is null?: {}",
271 overlapped_ptr.is_null()
272 ));
273 log(&format!(
274 "[WSASend] completion routine ptr is null?: {}",
275 completion_routine_ptr.is_null()
276 ));
277
278 let mut payloads = Vec::new();
279
280 for i in 0..buffer_count {
281 let wsabuf = buffers_ptr.add(i).read();
282 let length = wsabuf.len as usize;
283 let buf_ptr = wsabuf.buf;
284
285 if !buf_ptr.is_null() && length > 0 {
286 let data = std::slice::from_raw_parts(buf_ptr, length);
287 payloads.push(data.to_vec());
288 let snippet =
289 std::str::from_utf8(&data[..data.len().min(100)]).unwrap_or("<non-utf8>");
290 log(&format!("[WSASend] Buffer {i} (len {length}): {snippet}"));
291 } else {
292 log(&format!("[WSASend] Buffer {i}: null or zero-length"));
293 }
294 }
295
296 if payloads.len() > 1 {
297 unimplemented!("handle multiple WSABUFs arriving in WSASend")
298 }
299
300 let request = payloads
301 .into_iter()
302 .next()
303 .expect("At least one WSABUF is expected when WSASend is called");
304
305 log(&format!("[WSASend parsed request: {:?}", request.clone()));
306
307 connections::record_request(connection.socket.id, request.clone());
308 let graph_step = parse_send_graph_step(request.clone(), "WSASend");
309 pipe::send(&PipeEvent::NetGraph(graph_step.clone()));
310
311 match &connection.socket.data {
313 SocketData::ServerManaged => {
314 capture_and_send_callstack("WSASend");
316
317 if let GraphStep::Send(_, SendReact { success: false }) = graph_step {
320 let retval = Retval::Int(SOCKET_ERROR);
321 let error_code = WSAECONNRESET;
322 unimplemented!("TODO: handle rejecting the send");
323 } else {
325 let exchange = parse_exchange(request, "WSASend");
326 connection.socket.push_exchange(exchange.clone());
327 }
328 }
329 _ => unimplemented!("Only server managed sockets are implemented for WSASend"),
330 }
331 }
332}
333
334#[unsafe(no_mangle)]
336pub unsafe extern "C" fn pre_send(wrapctx: *mut c_void, _user_data: *mut *mut c_void) {
337 unsafe {
338 let socket_id = drwrap::get_arg(wrapctx, 0) as usize;
340 let payload_ptr = drwrap::get_arg(wrapctx, 1);
341 let payload_size = drwrap::get_arg(wrapctx, 2) as usize;
342
343 let stdcall_args_size = if cfg!(target_pointer_width = "32") {
345 16
347 } else {
348 0
349 };
350
351 let connection = connection_of_socket(socket_id, "send");
352
353 let request = match drcore::safe_read(payload_ptr, payload_size) {
354 Ok(payload) => {
355 log(&format!(
356 "[send] target: {}, payload: {:?}",
357 connection.to_string(),
358 payload
359 ));
360 payload
361 }
362 Err(read_err) => {
363 panic!(
364 "[send] Failed to read the payload sent by the binary: {:?}",
365 read_err
366 );
367 }
368 };
369
370 connections::record_request(connection.socket.id, request.clone());
371 let graph_step = parse_send_graph_step(request.clone(), "send");
372 pipe::send(&PipeEvent::NetGraph(graph_step.clone()));
373
374 if let GraphStep::Send(_, SendReact { success: false }) = graph_step {
377 let retval = Retval::Int(SOCKET_ERROR);
378 skip_call_helper(wrapctx, retval, "send", stdcall_args_size);
379 } else {
380 let exchange = parse_exchange(request.clone(), "send");
381 connection.socket.push_exchange(exchange);
382 match connection.socket.data {
383 SocketData::Sync(_) => {
384 let retval = Retval::Int(request.len() as i32);
385 skip_call_helper(wrapctx, retval, "send", stdcall_args_size);
386 }
387 SocketData::ServerManaged => (),
388 }
389 }
390 }
391}
392
393#[unsafe(no_mangle)]
394pub unsafe extern "C" fn pre_wsarecv(_wrapctx: *mut c_void, _user_data: *mut *mut c_void) {
395 unsafe {
396 log("[WSARecv] Pre-callback called *********");
397 }
399}
400
401#[unsafe(no_mangle)]
403pub extern "C" fn pre_recv(wrapctx: *mut c_void, _user_data: *mut *mut c_void) {
404 unsafe {
405 let socket_id = drwrap::get_arg(wrapctx, 0) as usize;
407
408 let stdcall_args_size = if cfg!(target_pointer_width = "32") {
410 16
412 } else {
413 0
414 };
415
416 let connection = connection_of_socket(socket_id, "recv");
417
418 log(&format!(
419 "[recv] called for: {}",
420 connection.addr.to_string()
421 ));
422 let buf_ptr = drwrap::get_arg(wrapctx, 1);
424 let buf_size = drwrap::get_arg(wrapctx, 2) as usize;
426
427 capture_and_send_callstack("recv");
428
429 match &connection.socket.data {
430 SocketData::Sync(recv_buf) => {
431 let mut counter = 100;
433 while recv_buf.is_empty() {
434 counter -= 1;
435 ffi::dr_sleep(100);
436 if counter <= 0 {
437 let retval = Retval::Int(SOCKET_ERROR);
439 skip_call_helper(wrapctx, retval, "recv", stdcall_args_size);
440 return ();
441 }
442 }
443 let mut exch_slot_guard = recv_buf
444 .slot
445 .lock()
446 .expect("Recv queue mutex should not be poisoned");
447
448 let exch = exch_slot_guard
449 .take()
450 .expect("while loop exits when recv_buf.is_some");
451
452 match &exch.response {
453 HalfDuplexResponse::SocketError => {
454 let graph_step = GraphStep::Recv(
455 RecvAttempt {
456 payload: exch.request.clone(),
457 },
458 RecvReact::SocketError,
459 );
460 let retval = Retval::Int(SOCKET_ERROR);
461 pipe::send(&PipeEvent::NetGraph(graph_step));
462 skip_call_helper(wrapctx, retval, "recv", stdcall_args_size);
463 }
464 HalfDuplexResponse::Payload(response) => {
465 assert!(response.len() <= buf_size);
466 match drcore::safe_write(buf_ptr, response.clone()) {
468 Ok(()) => log("[recv] successfully wrote to recv buffer"),
469 Err(write_err) => {
470 panic!("[recv] Failed writing payload: {:?}", write_err);
471 }
472 };
473 let graph_step = GraphStep::Recv(
474 RecvAttempt {
475 payload: exch.request.clone(),
476 },
477 RecvReact::Payload(response.clone()),
478 );
479 pipe::send(&PipeEvent::NetGraph(graph_step));
480 connections::record_response(socket_id, response.clone());
481 let retval = Retval::Int(response.len() as i32);
482 skip_call_helper(wrapctx, retval, "recv", stdcall_args_size);
483 }
484 };
485 }
486 SocketData::ServerManaged => (),
488 };
489 }
490}
491
492fn parse_socket(wrapctx: *mut c_void, wrapper_name: &str) -> (usize, SocketAddr) {
493 let socket_id = drwrap::get_arg(wrapctx, 0) as usize;
495 let sockaddr_ptr = drwrap::get_arg(wrapctx, 1);
497 let sockaddr_size = drwrap::get_arg(wrapctx, 2) as usize;
499
500 if sockaddr_ptr.is_null() || sockaddr_size == 0 {
501 todo!("[{wrapper_name}] Couldn't parse an IP address during Connect, ticket #8")
502 }
503
504 let socket_addr = match drcore::safe_read(sockaddr_ptr, sockaddr_size) {
506 Ok(buf) => SocketAddr::from_buf(buf),
507 Err(read_err) => {
508 log(&format!(
509 "[{wrapper_name}] Failed reading sockaddr:\n {:?}",
510 read_err
511 ));
512 None
513 }
514 };
515
516 let socket_addr = socket_addr.unwrap_or_else(|| {
517 todo!("[{wrapper_name}] Couldn't parse an IP address during Connect, ticket #8")
518 });
519 log(&format!(
520 "[{wrapper_name}] attempt to connect to {}",
521 socket_addr.to_string()
522 ));
523 (socket_id, socket_addr)
524}
525
526fn parse_connect_graph_step(socket_addr: SocketAddr, wrapper_name: &str) -> GraphStep {
527 let observed_attempt = Attempt::Connect(ConnectAttempt { addr: socket_addr });
528 match parse_planned_step(observed_attempt, wrapper_name) {
529 PlanningStep::Graph(graph_step) => graph_step,
530 _ => unreachable!("PlanningStep will be a GraphStep::Connect"),
531 }
532}
533
534fn parse_exchange(request: Vec<u8>, wrapper_name: &str) -> HalfDuplexExchange {
535 let observed_attempt = Attempt::Recv(RecvAttempt {
536 payload: request.clone(),
537 });
538 let planned_step = parse_planned_step(observed_attempt, wrapper_name);
539
540 match planned_step {
541 PlanningStep::Graph(GraphStep::Recv(recv_attempt, recv_react)) => match recv_react {
542 RecvReact::SocketError => HalfDuplexExchange {
543 request: recv_attempt.payload,
544 response: HalfDuplexResponse::SocketError,
545 },
546 RecvReact::Payload(p) => HalfDuplexExchange {
547 request: recv_attempt.payload,
548 response: HalfDuplexResponse::Payload(p),
549 },
550 },
551 PlanningStep::Fuzz(recv_attempt) => HalfDuplexExchange {
552 request: recv_attempt.payload.clone(),
553 response: HalfDuplexResponse::Payload(
554 navigator::protocol::get().fuzz_response(&recv_attempt.payload, Some(log)),
555 ),
556 },
557 _ => unreachable!(
558 "GraphStep will be the Recv variant when an Attempt::Recv is the argument to parse_planning_step"
559 ),
560 }
561}
562
563fn parse_send_graph_step(request: Vec<u8>, wrapper_name: &str) -> GraphStep {
564 let observed_attempt = Attempt::Send(SendAttempt { payload: request });
565 let planned_step = parse_planned_step(observed_attempt, wrapper_name);
566 match planned_step {
567 PlanningStep::Graph(graph_step @ GraphStep::Send(_, _)) => graph_step,
568 _ => unreachable!(
569 "Planned step will be Send when parse_planned_step is called with an Attempt::Send"
570 ),
571 }
572}
573
574unsafe fn capture_and_send_callstack(wrapper_name: &str) {
575 unsafe {
576 let drctx = ffi::dr_get_current_drcontext();
578 let callstack = instrument::stack_frames(drctx).map(|frames| {
579 let (base, size) = instrument::module_bounds();
580 CallStack::from_raw_frames(base, size, frames)
581 });
582 log(&format!(
583 "[{wrapper_name}] callstack recorded: {}",
584 if callstack.is_some() { "Some" } else { "None" }
585 ));
586 if callstack.is_some() {
587 pipe::send(&PipeEvent::CallStack(callstack.clone().unwrap()));
588 }
589 }
590}
591
592unsafe fn skip_call_helper(
593 wrapctx: *mut c_void,
594 retval: Retval,
595 wrapper_name: &str,
596 args_size: usize,
597) {
598 unsafe {
599 match drwrap::skip_call(wrapctx, retval.clone(), args_size) {
600 true => {
601 log(&format!(
602 "[{wrapper_name}] Successfully returned {:?}",
603 retval
604 ));
605 }
606 false => panic!("[{wrapper_name}] skip_call failed"),
607 };
608 }
609}
610
611fn parse_planned_step(observed_attempt: Attempt, wrapper_name: &str) -> PlanningStep {
614 match trajectory::next(&observed_attempt) {
616 Ok(Some(current_step)) => {
617 log(&format!(
618 "[{wrapper_name}] following configured trajectory, current step: {:?}",
619 current_step
620 ));
621 current_step
622 }
623 Ok(None) | Err(_) => {
625 let default_react = React::default(&observed_attempt, Some(log));
626 let default_step =
627 PlanningStep::Graph(GraphStep::new(observed_attempt, default_react).expect(
628 "React::default ensures that the default_react matches the attempt type",
629 ));
630 default_step
631 }
632 }
633}