Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 54 additions & 19 deletions litebox_syscall_rewriter/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -191,6 +191,7 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option<u64>) -> Res
trampoline_data.extend_from_slice(&trampoline.to_le_bytes());
// Patch syscalls in-place in buf
let mut skipped_addrs = Vec::new();
let mut syscall_insns_found = false;
for s in &text_sections {
let section_data = section_slice_mut(buf, s)?;
match hook_syscalls_in_section(
Expand All@@ -202,13 +203,34 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option<u64>) -> Res
trampoline_base_addr, // entry point is at offset 0 of trampoline
&mut trampoline_data,
) {
Ok(addrs) => skipped_addrs.extend(addrs),
Ok(addrs) => {
skipped_addrs.extend(addrs);
syscall_insns_found = true;
}
Err(InternalError::NoSyscallInstructionsFound) => {}
Err(InternalError::Public(e)) => return Err(e),
Err(e) => unreachable!("unexpected internal error: {e:?}"),
}
}

if !syscall_insns_found {
// No syscall instructions found. Append a header-only marker so the
// loader can distinguish "checked by rewriter, nothing to patch" from
// "never processed." The trampoline_size=0 sentinel tells the loader
// to skip trampoline mapping entirely.
// Use the original input (not `buf`) to avoid emitting the phdr
// alignment fixup that is only needed for the `object` crate parser.
let mut out = input_binary.to_vec();
let header = TrampolineHeader64 {
magic: *TRAMPOLINE_MAGIC,
file_offset: 0,
vaddr: 0,
trampoline_size: 0,
};
out.extend_from_slice(header.as_bytes());
return Ok(out);
}

// Build output: [patched ELF][padding to page boundary][trampoline code][header]
let mut out = buf.to_vec();
let remain = out.len() % 0x1000;
Expand DownExpand Up@@ -293,7 +315,9 @@ fn is_already_hooked(input_binary: &[u8], arch: Arch) -> bool {
(header.file_offset, header.vaddr, header.trampoline_size);

if trampoline_size == 0 {
return false;
// Size=0 sentinel: the rewriter processed this binary but found no
// syscall instructions. It is already hooked (nothing to do).
return true;
}
if file_offset % 0x1000 != 0 {
return false;
Expand DownExpand Up@@ -431,18 +455,13 @@ fn hook_syscalls_in_section(
trampoline_data.extend_from_slice(&presyscall_bytes);

let return_addr = inst.next_ip();
// Put jump back location into rcx.
let jmp_back_base = checked_add_u64(
trampoline_base_addr,
trampoline_data.len() as u64 + 7,
"x86_64 trampoline jump-back base",
)?;
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32]
trampoline_data.extend_from_slice(&rel32_bytes(
return_addr,
jmp_back_base,
"x86_64 trampoline jump-back",
)?);

// LEA RCX, [RIP + 6] — load RCX with the address of the in-trampoline
// `post_jmp` (the instruction immediately after the indirect JMP into
// the callback). The SA_RESTART handler relies on the invariant that
// pt_regs.rcx - 6 points at the indirect JMP itself, so it can rewind
// ctx.rip and re-enter the callback.
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D, 0x06, 0x00, 0x00, 0x00]);

// Add jmp [rip + offset_to_entry_point]
trampoline_data.extend_from_slice(&[0xFF, 0x25]);
Expand All@@ -459,6 +478,20 @@ fn hook_syscalls_in_section(
"x86_64 trampoline entry",
)?);

// post_jmp: JMP rel32 back to the guest instruction following the
// original syscall. The callback returns via `jmp rcx` and lands here.
let jmp_back_base = checked_add_u64(
trampoline_base_addr,
trampoline_data.len() as u64 + 5,
"x86_64 trampoline jump-back base",
)?;
trampoline_data.push(0xE9);
trampoline_data.extend_from_slice(&rel32_bytes(
return_addr,
jmp_back_base,
"x86_64 trampoline jump-back",
)?);

// Replace original instructions with jump to trampoline
let replace_offset = usize::try_from(replace_start - section_base_addr).unwrap();
section_data[replace_offset] = 0xE9; // JMP rel32
Expand DownExpand Up@@ -538,8 +571,8 @@ fn fixup_phdr_alignment(buf: &mut [u8]) {
return;
};

if old_end > buf.len() || new_end > buf.len() {
return; // corrupt phdr table or not enough room
if new_end > buf.len() {
return; // not enough room
}

// Only relocate when the overwritten bytes are padding. Otherwise this would corrupt the file
Expand DownExpand Up@@ -933,9 +966,11 @@ fn hook_syscall_and_after(
Vec::new()
};

// Put jump back location into rcx, via lea rcx, [next instruction]
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32]
trampoline_data.extend_from_slice(&6u32.to_le_bytes());
// LEA RCX, [RIP + 6] — make RCX point at the instruction immediately
// following the indirect JMP: the start of postsyscall_bytes (or, when
// none, the unconditional JMP back to guest). The SA_RESTART handler
// relies on pt_regs.rcx - 6 pointing at the indirect JMP itself.
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D, 0x06, 0x00, 0x00, 0x00]);
// Add jmp [rip + offset_to_entry_point]
trampoline_data.extend_from_slice(&[0xFF, 0x25]);
// RIP after this instruction = trampoline_base_addr + trampoline_data.len() + 4
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Syscall rewriter improvements by wdcui · Pull Request #812 · microsoft/litebox · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 54 additions & 19 deletions litebox_syscall_rewriter/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -191,6 +191,7 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option<u64>) -> Res
trampoline_data.extend_from_slice(&trampoline.to_le_bytes());
// Patch syscalls in-place in buf
let mut skipped_addrs = Vec::new();
let mut syscall_insns_found = false;
for s in &text_sections {
let section_data = section_slice_mut(buf, s)?;
match hook_syscalls_in_section(
Expand All@@ -202,13 +203,34 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option<u64>) -> Res
trampoline_base_addr, // entry point is at offset 0 of trampoline
&mut trampoline_data,
) {
Ok(addrs) => skipped_addrs.extend(addrs),
Ok(addrs) => {
skipped_addrs.extend(addrs);
syscall_insns_found = true;
}
Err(InternalError::NoSyscallInstructionsFound) => {}
Err(InternalError::Public(e)) => return Err(e),
Err(e) => unreachable!("unexpected internal error: {e:?}"),
}
}

if !syscall_insns_found {
// No syscall instructions found. Append a header-only marker so the
// loader can distinguish "checked by rewriter, nothing to patch" from
// "never processed." The trampoline_size=0 sentinel tells the loader
// to skip trampoline mapping entirely.
// Use the original input (not `buf`) to avoid emitting the phdr
// alignment fixup that is only needed for the `object` crate parser.
let mut out = input_binary.to_vec();
let header = TrampolineHeader64 {
magic: *TRAMPOLINE_MAGIC,
file_offset: 0,
vaddr: 0,
trampoline_size: 0,
};
out.extend_from_slice(header.as_bytes());
return Ok(out);
}

// Build output: [patched ELF][padding to page boundary][trampoline code][header]
let mut out = buf.to_vec();
let remain = out.len() % 0x1000;
Expand DownExpand Up@@ -293,7 +315,9 @@ fn is_already_hooked(input_binary: &[u8], arch: Arch) -> bool {
(header.file_offset, header.vaddr, header.trampoline_size);

if trampoline_size == 0 {
return false;
// Size=0 sentinel: the rewriter processed this binary but found no
// syscall instructions. It is already hooked (nothing to do).
return true;
}
if file_offset % 0x1000 != 0 {
return false;
Expand DownExpand Up@@ -431,18 +455,13 @@ fn hook_syscalls_in_section(
trampoline_data.extend_from_slice(&presyscall_bytes);

let return_addr = inst.next_ip();
// Put jump back location into rcx.
let jmp_back_base = checked_add_u64(
trampoline_base_addr,
trampoline_data.len() as u64 + 7,
"x86_64 trampoline jump-back base",
)?;
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32]
trampoline_data.extend_from_slice(&rel32_bytes(
return_addr,
jmp_back_base,
"x86_64 trampoline jump-back",
)?);

// LEA RCX, [RIP + 6] — load RCX with the address of the in-trampoline
// `post_jmp` (the instruction immediately after the indirect JMP into
// the callback). The SA_RESTART handler relies on the invariant that
// pt_regs.rcx - 6 points at the indirect JMP itself, so it can rewind
// ctx.rip and re-enter the callback.
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D, 0x06, 0x00, 0x00, 0x00]);

// Add jmp [rip + offset_to_entry_point]
trampoline_data.extend_from_slice(&[0xFF, 0x25]);
Expand All@@ -459,6 +478,20 @@ fn hook_syscalls_in_section(
"x86_64 trampoline entry",
)?);

// post_jmp: JMP rel32 back to the guest instruction following the
// original syscall. The callback returns via `jmp rcx` and lands here.
let jmp_back_base = checked_add_u64(
trampoline_base_addr,
trampoline_data.len() as u64 + 5,
"x86_64 trampoline jump-back base",
)?;
trampoline_data.push(0xE9);
trampoline_data.extend_from_slice(&rel32_bytes(
return_addr,
jmp_back_base,
"x86_64 trampoline jump-back",
)?);

// Replace original instructions with jump to trampoline
let replace_offset = usize::try_from(replace_start - section_base_addr).unwrap();
section_data[replace_offset] = 0xE9; // JMP rel32
Expand DownExpand Up@@ -538,8 +571,8 @@ fn fixup_phdr_alignment(buf: &mut [u8]) {
return;
};

if old_end > buf.len() || new_end > buf.len() {
return; // corrupt phdr table or not enough room
if new_end > buf.len() {
return; // not enough room
}

// Only relocate when the overwritten bytes are padding. Otherwise this would corrupt the file
Expand DownExpand Up@@ -933,9 +966,11 @@ fn hook_syscall_and_after(
Vec::new()
};

// Put jump back location into rcx, via lea rcx, [next instruction]
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32]
trampoline_data.extend_from_slice(&6u32.to_le_bytes());
// LEA RCX, [RIP + 6] — make RCX point at the instruction immediately
// following the indirect JMP: the start of postsyscall_bytes (or, when
// none, the unconditional JMP back to guest). The SA_RESTART handler
// relies on pt_regs.rcx - 6 pointing at the indirect JMP itself.
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D, 0x06, 0x00, 0x00, 0x00]);
// Add jmp [rip + offset_to_entry_point]
trampoline_data.extend_from_slice(&[0xFF, 0x25]);
// RIP after this instruction = trampoline_base_addr + trampoline_data.len() + 4
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Syscall rewriter improvements by wdcui · Pull Request #812 · microsoft/litebox · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 54 additions & 19 deletions litebox_syscall_rewriter/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -191,6 +191,7 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option<u64>) -> Res
trampoline_data.extend_from_slice(&trampoline.to_le_bytes());
// Patch syscalls in-place in buf
let mut skipped_addrs = Vec::new();
let mut syscall_insns_found = false;
for s in &text_sections {
let section_data = section_slice_mut(buf, s)?;
match hook_syscalls_in_section(
Expand All@@ -202,13 +203,34 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option<u64>) -> Res
trampoline_base_addr, // entry point is at offset 0 of trampoline
&mut trampoline_data,
) {
Ok(addrs) => skipped_addrs.extend(addrs),
Ok(addrs) => {
skipped_addrs.extend(addrs);
syscall_insns_found = true;
}
Err(InternalError::NoSyscallInstructionsFound) => {}
Err(InternalError::Public(e)) => return Err(e),
Err(e) => unreachable!("unexpected internal error: {e:?}"),
}
}

if !syscall_insns_found {
// No syscall instructions found. Append a header-only marker so the
// loader can distinguish "checked by rewriter, nothing to patch" from
// "never processed." The trampoline_size=0 sentinel tells the loader
// to skip trampoline mapping entirely.
// Use the original input (not `buf`) to avoid emitting the phdr
// alignment fixup that is only needed for the `object` crate parser.
let mut out = input_binary.to_vec();
let header = TrampolineHeader64 {
magic: *TRAMPOLINE_MAGIC,
file_offset: 0,
vaddr: 0,
trampoline_size: 0,
};
out.extend_from_slice(header.as_bytes());
return Ok(out);
}

// Build output: [patched ELF][padding to page boundary][trampoline code][header]
let mut out = buf.to_vec();
let remain = out.len() % 0x1000;
Expand DownExpand Up@@ -293,7 +315,9 @@ fn is_already_hooked(input_binary: &[u8], arch: Arch) -> bool {
(header.file_offset, header.vaddr, header.trampoline_size);

if trampoline_size == 0 {
return false;
// Size=0 sentinel: the rewriter processed this binary but found no
// syscall instructions. It is already hooked (nothing to do).
return true;
}
if file_offset % 0x1000 != 0 {
return false;
Expand DownExpand Up@@ -431,18 +455,13 @@ fn hook_syscalls_in_section(
trampoline_data.extend_from_slice(&presyscall_bytes);

let return_addr = inst.next_ip();
// Put jump back location into rcx.
let jmp_back_base = checked_add_u64(
trampoline_base_addr,
trampoline_data.len() as u64 + 7,
"x86_64 trampoline jump-back base",
)?;
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32]
trampoline_data.extend_from_slice(&rel32_bytes(
return_addr,
jmp_back_base,
"x86_64 trampoline jump-back",
)?);

// LEA RCX, [RIP + 6] — load RCX with the address of the in-trampoline
// `post_jmp` (the instruction immediately after the indirect JMP into
// the callback). The SA_RESTART handler relies on the invariant that
// pt_regs.rcx - 6 points at the indirect JMP itself, so it can rewind
// ctx.rip and re-enter the callback.
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D, 0x06, 0x00, 0x00, 0x00]);

// Add jmp [rip + offset_to_entry_point]
trampoline_data.extend_from_slice(&[0xFF, 0x25]);
Expand All@@ -459,6 +478,20 @@ fn hook_syscalls_in_section(
"x86_64 trampoline entry",
)?);

// post_jmp: JMP rel32 back to the guest instruction following the
// original syscall. The callback returns via `jmp rcx` and lands here.
let jmp_back_base = checked_add_u64(
trampoline_base_addr,
trampoline_data.len() as u64 + 5,
"x86_64 trampoline jump-back base",
)?;
trampoline_data.push(0xE9);
trampoline_data.extend_from_slice(&rel32_bytes(
return_addr,
jmp_back_base,
"x86_64 trampoline jump-back",
)?);

// Replace original instructions with jump to trampoline
let replace_offset = usize::try_from(replace_start - section_base_addr).unwrap();
section_data[replace_offset] = 0xE9; // JMP rel32
Expand DownExpand Up@@ -538,8 +571,8 @@ fn fixup_phdr_alignment(buf: &mut [u8]) {
return;
};

if old_end > buf.len() || new_end > buf.len() {
return; // corrupt phdr table or not enough room
if new_end > buf.len() {
return; // not enough room
}

// Only relocate when the overwritten bytes are padding. Otherwise this would corrupt the file
Expand DownExpand Up@@ -933,9 +966,11 @@ fn hook_syscall_and_after(
Vec::new()
};

// Put jump back location into rcx, via lea rcx, [next instruction]
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32]
trampoline_data.extend_from_slice(&6u32.to_le_bytes());
// LEA RCX, [RIP + 6] — make RCX point at the instruction immediately
// following the indirect JMP: the start of postsyscall_bytes (or, when
// none, the unconditional JMP back to guest). The SA_RESTART handler
// relies on pt_regs.rcx - 6 pointing at the indirect JMP itself.
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D, 0x06, 0x00, 0x00, 0x00]);
// Add jmp [rip + offset_to_entry_point]
trampoline_data.extend_from_slice(&[0xFF, 0x25]);
// RIP after this instruction = trampoline_base_addr + trampoline_data.len() + 4
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Syscall rewriter improvements by wdcui · Pull Request #812 · microsoft/litebox · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 54 additions & 19 deletions litebox_syscall_rewriter/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -191,6 +191,7 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option<u64>) -> Res
trampoline_data.extend_from_slice(&trampoline.to_le_bytes());
// Patch syscalls in-place in buf
let mut skipped_addrs = Vec::new();
let mut syscall_insns_found = false;
for s in &text_sections {
let section_data = section_slice_mut(buf, s)?;
match hook_syscalls_in_section(
Expand All@@ -202,13 +203,34 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option<u64>) -> Res
trampoline_base_addr, // entry point is at offset 0 of trampoline
&mut trampoline_data,
) {
Ok(addrs) => skipped_addrs.extend(addrs),
Ok(addrs) => {
skipped_addrs.extend(addrs);
syscall_insns_found = true;
}
Err(InternalError::NoSyscallInstructionsFound) => {}
Err(InternalError::Public(e)) => return Err(e),
Err(e) => unreachable!("unexpected internal error: {e:?}"),
}
}

if !syscall_insns_found {
// No syscall instructions found. Append a header-only marker so the
// loader can distinguish "checked by rewriter, nothing to patch" from
// "never processed." The trampoline_size=0 sentinel tells the loader
// to skip trampoline mapping entirely.
// Use the original input (not `buf`) to avoid emitting the phdr
// alignment fixup that is only needed for the `object` crate parser.
let mut out = input_binary.to_vec();
let header = TrampolineHeader64 {
magic: *TRAMPOLINE_MAGIC,
file_offset: 0,
vaddr: 0,
trampoline_size: 0,
};
out.extend_from_slice(header.as_bytes());
return Ok(out);
}

// Build output: [patched ELF][padding to page boundary][trampoline code][header]
let mut out = buf.to_vec();
let remain = out.len() % 0x1000;
Expand DownExpand Up@@ -293,7 +315,9 @@ fn is_already_hooked(input_binary: &[u8], arch: Arch) -> bool {
(header.file_offset, header.vaddr, header.trampoline_size);

if trampoline_size == 0 {
return false;
// Size=0 sentinel: the rewriter processed this binary but found no
// syscall instructions. It is already hooked (nothing to do).
return true;
}
if file_offset % 0x1000 != 0 {
return false;
Expand DownExpand Up@@ -431,18 +455,13 @@ fn hook_syscalls_in_section(
trampoline_data.extend_from_slice(&presyscall_bytes);

let return_addr = inst.next_ip();
// Put jump back location into rcx.
let jmp_back_base = checked_add_u64(
trampoline_base_addr,
trampoline_data.len() as u64 + 7,
"x86_64 trampoline jump-back base",
)?;
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32]
trampoline_data.extend_from_slice(&rel32_bytes(
return_addr,
jmp_back_base,
"x86_64 trampoline jump-back",
)?);

// LEA RCX, [RIP + 6] — load RCX with the address of the in-trampoline
// `post_jmp` (the instruction immediately after the indirect JMP into
// the callback). The SA_RESTART handler relies on the invariant that
// pt_regs.rcx - 6 points at the indirect JMP itself, so it can rewind
// ctx.rip and re-enter the callback.
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D, 0x06, 0x00, 0x00, 0x00]);

// Add jmp [rip + offset_to_entry_point]
trampoline_data.extend_from_slice(&[0xFF, 0x25]);
Expand All@@ -459,6 +478,20 @@ fn hook_syscalls_in_section(
"x86_64 trampoline entry",
)?);

// post_jmp: JMP rel32 back to the guest instruction following the
// original syscall. The callback returns via `jmp rcx` and lands here.
let jmp_back_base = checked_add_u64(
trampoline_base_addr,
trampoline_data.len() as u64 + 5,
"x86_64 trampoline jump-back base",
)?;
trampoline_data.push(0xE9);
trampoline_data.extend_from_slice(&rel32_bytes(
return_addr,
jmp_back_base,
"x86_64 trampoline jump-back",
)?);

// Replace original instructions with jump to trampoline
let replace_offset = usize::try_from(replace_start - section_base_addr).unwrap();
section_data[replace_offset] = 0xE9; // JMP rel32
Expand DownExpand Up@@ -538,8 +571,8 @@ fn fixup_phdr_alignment(buf: &mut [u8]) {
return;
};

if old_end > buf.len() || new_end > buf.len() {
return; // corrupt phdr table or not enough room
if new_end > buf.len() {
return; // not enough room
}

// Only relocate when the overwritten bytes are padding. Otherwise this would corrupt the file
Expand DownExpand Up@@ -933,9 +966,11 @@ fn hook_syscall_and_after(
Vec::new()
};

// Put jump back location into rcx, via lea rcx, [next instruction]
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32]
trampoline_data.extend_from_slice(&6u32.to_le_bytes());
// LEA RCX, [RIP + 6] — make RCX point at the instruction immediately
// following the indirect JMP: the start of postsyscall_bytes (or, when
// none, the unconditional JMP back to guest). The SA_RESTART handler
// relies on pt_regs.rcx - 6 pointing at the indirect JMP itself.
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D, 0x06, 0x00, 0x00, 0x00]);
// Add jmp [rip + offset_to_entry_point]
trampoline_data.extend_from_slice(&[0xFF, 0x25]);
// RIP after this instruction = trampoline_base_addr + trampoline_data.len() + 4
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Syscall rewriter improvements by wdcui · Pull Request #812 · microsoft/litebox · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 54 additions & 19 deletions litebox_syscall_rewriter/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -191,6 +191,7 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option<u64>) -> Res
trampoline_data.extend_from_slice(&trampoline.to_le_bytes());
// Patch syscalls in-place in buf
let mut skipped_addrs = Vec::new();
let mut syscall_insns_found = false;
for s in &text_sections {
let section_data = section_slice_mut(buf, s)?;
match hook_syscalls_in_section(
Expand All@@ -202,13 +203,34 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option<u64>) -> Res
trampoline_base_addr, // entry point is at offset 0 of trampoline
&mut trampoline_data,
) {
Ok(addrs) => skipped_addrs.extend(addrs),
Ok(addrs) => {
skipped_addrs.extend(addrs);
syscall_insns_found = true;
}
Err(InternalError::NoSyscallInstructionsFound) => {}
Err(InternalError::Public(e)) => return Err(e),
Err(e) => unreachable!("unexpected internal error: {e:?}"),
}
}

if !syscall_insns_found {
// No syscall instructions found. Append a header-only marker so the
// loader can distinguish "checked by rewriter, nothing to patch" from
// "never processed." The trampoline_size=0 sentinel tells the loader
// to skip trampoline mapping entirely.
// Use the original input (not `buf`) to avoid emitting the phdr
// alignment fixup that is only needed for the `object` crate parser.
let mut out = input_binary.to_vec();
let header = TrampolineHeader64 {
magic: *TRAMPOLINE_MAGIC,
file_offset: 0,
vaddr: 0,
trampoline_size: 0,
};
out.extend_from_slice(header.as_bytes());
return Ok(out);
}

// Build output: [patched ELF][padding to page boundary][trampoline code][header]
let mut out = buf.to_vec();
let remain = out.len() % 0x1000;
Expand DownExpand Up@@ -293,7 +315,9 @@ fn is_already_hooked(input_binary: &[u8], arch: Arch) -> bool {
(header.file_offset, header.vaddr, header.trampoline_size);

if trampoline_size == 0 {
return false;
// Size=0 sentinel: the rewriter processed this binary but found no
// syscall instructions. It is already hooked (nothing to do).
return true;
}
if file_offset % 0x1000 != 0 {
return false;
Expand DownExpand Up@@ -431,18 +455,13 @@ fn hook_syscalls_in_section(
trampoline_data.extend_from_slice(&presyscall_bytes);

let return_addr = inst.next_ip();
// Put jump back location into rcx.
let jmp_back_base = checked_add_u64(
trampoline_base_addr,
trampoline_data.len() as u64 + 7,
"x86_64 trampoline jump-back base",
)?;
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32]
trampoline_data.extend_from_slice(&rel32_bytes(
return_addr,
jmp_back_base,
"x86_64 trampoline jump-back",
)?);

// LEA RCX, [RIP + 6] — load RCX with the address of the in-trampoline
// `post_jmp` (the instruction immediately after the indirect JMP into
// the callback). The SA_RESTART handler relies on the invariant that
// pt_regs.rcx - 6 points at the indirect JMP itself, so it can rewind
// ctx.rip and re-enter the callback.
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D, 0x06, 0x00, 0x00, 0x00]);

// Add jmp [rip + offset_to_entry_point]
trampoline_data.extend_from_slice(&[0xFF, 0x25]);
Expand All@@ -459,6 +478,20 @@ fn hook_syscalls_in_section(
"x86_64 trampoline entry",
)?);

// post_jmp: JMP rel32 back to the guest instruction following the
// original syscall. The callback returns via `jmp rcx` and lands here.
let jmp_back_base = checked_add_u64(
trampoline_base_addr,
trampoline_data.len() as u64 + 5,
"x86_64 trampoline jump-back base",
)?;
trampoline_data.push(0xE9);
trampoline_data.extend_from_slice(&rel32_bytes(
return_addr,
jmp_back_base,
"x86_64 trampoline jump-back",
)?);

// Replace original instructions with jump to trampoline
let replace_offset = usize::try_from(replace_start - section_base_addr).unwrap();
section_data[replace_offset] = 0xE9; // JMP rel32
Expand DownExpand Up@@ -538,8 +571,8 @@ fn fixup_phdr_alignment(buf: &mut [u8]) {
return;
};

if old_end > buf.len() || new_end > buf.len() {
return; // corrupt phdr table or not enough room
if new_end > buf.len() {
return; // not enough room
}

// Only relocate when the overwritten bytes are padding. Otherwise this would corrupt the file
Expand DownExpand Up@@ -933,9 +966,11 @@ fn hook_syscall_and_after(
Vec::new()
};

// Put jump back location into rcx, via lea rcx, [next instruction]
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32]
trampoline_data.extend_from_slice(&6u32.to_le_bytes());
// LEA RCX, [RIP + 6] — make RCX point at the instruction immediately
// following the indirect JMP: the start of postsyscall_bytes (or, when
// none, the unconditional JMP back to guest). The SA_RESTART handler
// relies on pt_regs.rcx - 6 pointing at the indirect JMP itself.
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D, 0x06, 0x00, 0x00, 0x00]);
// Add jmp [rip + offset_to_entry_point]
trampoline_data.extend_from_slice(&[0xFF, 0x25]);
// RIP after this instruction = trampoline_base_addr + trampoline_data.len() + 4
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Syscall rewriter improvements by wdcui · Pull Request #812 · microsoft/litebox · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 54 additions & 19 deletions litebox_syscall_rewriter/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -191,6 +191,7 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option<u64>) -> Res
trampoline_data.extend_from_slice(&trampoline.to_le_bytes());
// Patch syscalls in-place in buf
let mut skipped_addrs = Vec::new();
let mut syscall_insns_found = false;
for s in &text_sections {
let section_data = section_slice_mut(buf, s)?;
match hook_syscalls_in_section(
Expand All@@ -202,13 +203,34 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option<u64>) -> Res
trampoline_base_addr, // entry point is at offset 0 of trampoline
&mut trampoline_data,
) {
Ok(addrs) => skipped_addrs.extend(addrs),
Ok(addrs) => {
skipped_addrs.extend(addrs);
syscall_insns_found = true;
}
Err(InternalError::NoSyscallInstructionsFound) => {}
Err(InternalError::Public(e)) => return Err(e),
Err(e) => unreachable!("unexpected internal error: {e:?}"),
}
}

if !syscall_insns_found {
// No syscall instructions found. Append a header-only marker so the
// loader can distinguish "checked by rewriter, nothing to patch" from
// "never processed." The trampoline_size=0 sentinel tells the loader
// to skip trampoline mapping entirely.
// Use the original input (not `buf`) to avoid emitting the phdr
// alignment fixup that is only needed for the `object` crate parser.
let mut out = input_binary.to_vec();
let header = TrampolineHeader64 {
magic: *TRAMPOLINE_MAGIC,
file_offset: 0,
vaddr: 0,
trampoline_size: 0,
};
out.extend_from_slice(header.as_bytes());
return Ok(out);
}

// Build output: [patched ELF][padding to page boundary][trampoline code][header]
let mut out = buf.to_vec();
let remain = out.len() % 0x1000;
Expand DownExpand Up@@ -293,7 +315,9 @@ fn is_already_hooked(input_binary: &[u8], arch: Arch) -> bool {
(header.file_offset, header.vaddr, header.trampoline_size);

if trampoline_size == 0 {
return false;
// Size=0 sentinel: the rewriter processed this binary but found no
// syscall instructions. It is already hooked (nothing to do).
return true;
}
if file_offset % 0x1000 != 0 {
return false;
Expand DownExpand Up@@ -431,18 +455,13 @@ fn hook_syscalls_in_section(
trampoline_data.extend_from_slice(&presyscall_bytes);

let return_addr = inst.next_ip();
// Put jump back location into rcx.
let jmp_back_base = checked_add_u64(
trampoline_base_addr,
trampoline_data.len() as u64 + 7,
"x86_64 trampoline jump-back base",
)?;
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32]
trampoline_data.extend_from_slice(&rel32_bytes(
return_addr,
jmp_back_base,
"x86_64 trampoline jump-back",
)?);

// LEA RCX, [RIP + 6] — load RCX with the address of the in-trampoline
// `post_jmp` (the instruction immediately after the indirect JMP into
// the callback). The SA_RESTART handler relies on the invariant that
// pt_regs.rcx - 6 points at the indirect JMP itself, so it can rewind
// ctx.rip and re-enter the callback.
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D, 0x06, 0x00, 0x00, 0x00]);

// Add jmp [rip + offset_to_entry_point]
trampoline_data.extend_from_slice(&[0xFF, 0x25]);
Expand All@@ -459,6 +478,20 @@ fn hook_syscalls_in_section(
"x86_64 trampoline entry",
)?);

// post_jmp: JMP rel32 back to the guest instruction following the
// original syscall. The callback returns via `jmp rcx` and lands here.
let jmp_back_base = checked_add_u64(
trampoline_base_addr,
trampoline_data.len() as u64 + 5,
"x86_64 trampoline jump-back base",
)?;
trampoline_data.push(0xE9);
trampoline_data.extend_from_slice(&rel32_bytes(
return_addr,
jmp_back_base,
"x86_64 trampoline jump-back",
)?);

// Replace original instructions with jump to trampoline
let replace_offset = usize::try_from(replace_start - section_base_addr).unwrap();
section_data[replace_offset] = 0xE9; // JMP rel32
Expand DownExpand Up@@ -538,8 +571,8 @@ fn fixup_phdr_alignment(buf: &mut [u8]) {
return;
};

if old_end > buf.len() || new_end > buf.len() {
return; // corrupt phdr table or not enough room
if new_end > buf.len() {
return; // not enough room
}

// Only relocate when the overwritten bytes are padding. Otherwise this would corrupt the file
Expand DownExpand Up@@ -933,9 +966,11 @@ fn hook_syscall_and_after(
Vec::new()
};

// Put jump back location into rcx, via lea rcx, [next instruction]
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32]
trampoline_data.extend_from_slice(&6u32.to_le_bytes());
// LEA RCX, [RIP + 6] — make RCX point at the instruction immediately
// following the indirect JMP: the start of postsyscall_bytes (or, when
// none, the unconditional JMP back to guest). The SA_RESTART handler
// relies on pt_regs.rcx - 6 pointing at the indirect JMP itself.
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D, 0x06, 0x00, 0x00, 0x00]);
// Add jmp [rip + offset_to_entry_point]
trampoline_data.extend_from_slice(&[0xFF, 0x25]);
// RIP after this instruction = trampoline_base_addr + trampoline_data.len() + 4
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Syscall rewriter improvements by wdcui · Pull Request #812 · microsoft/litebox · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 54 additions & 19 deletions litebox_syscall_rewriter/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -191,6 +191,7 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option<u64>) -> Res
trampoline_data.extend_from_slice(&trampoline.to_le_bytes());
// Patch syscalls in-place in buf
let mut skipped_addrs = Vec::new();
let mut syscall_insns_found = false;
for s in &text_sections {
let section_data = section_slice_mut(buf, s)?;
match hook_syscalls_in_section(
Expand All@@ -202,13 +203,34 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option<u64>) -> Res
trampoline_base_addr, // entry point is at offset 0 of trampoline
&mut trampoline_data,
) {
Ok(addrs) => skipped_addrs.extend(addrs),
Ok(addrs) => {
skipped_addrs.extend(addrs);
syscall_insns_found = true;
}
Err(InternalError::NoSyscallInstructionsFound) => {}
Err(InternalError::Public(e)) => return Err(e),
Err(e) => unreachable!("unexpected internal error: {e:?}"),
}
}

if !syscall_insns_found {
// No syscall instructions found. Append a header-only marker so the
// loader can distinguish "checked by rewriter, nothing to patch" from
// "never processed." The trampoline_size=0 sentinel tells the loader
// to skip trampoline mapping entirely.
// Use the original input (not `buf`) to avoid emitting the phdr
// alignment fixup that is only needed for the `object` crate parser.
let mut out = input_binary.to_vec();
let header = TrampolineHeader64 {
magic: *TRAMPOLINE_MAGIC,
file_offset: 0,
vaddr: 0,
trampoline_size: 0,
};
out.extend_from_slice(header.as_bytes());
return Ok(out);
}

// Build output: [patched ELF][padding to page boundary][trampoline code][header]
let mut out = buf.to_vec();
let remain = out.len() % 0x1000;
Expand DownExpand Up@@ -293,7 +315,9 @@ fn is_already_hooked(input_binary: &[u8], arch: Arch) -> bool {
(header.file_offset, header.vaddr, header.trampoline_size);

if trampoline_size == 0 {
return false;
// Size=0 sentinel: the rewriter processed this binary but found no
// syscall instructions. It is already hooked (nothing to do).
return true;
}
if file_offset % 0x1000 != 0 {
return false;
Expand DownExpand Up@@ -431,18 +455,13 @@ fn hook_syscalls_in_section(
trampoline_data.extend_from_slice(&presyscall_bytes);

let return_addr = inst.next_ip();
// Put jump back location into rcx.
let jmp_back_base = checked_add_u64(
trampoline_base_addr,
trampoline_data.len() as u64 + 7,
"x86_64 trampoline jump-back base",
)?;
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32]
trampoline_data.extend_from_slice(&rel32_bytes(
return_addr,
jmp_back_base,
"x86_64 trampoline jump-back",
)?);

// LEA RCX, [RIP + 6] — load RCX with the address of the in-trampoline
// `post_jmp` (the instruction immediately after the indirect JMP into
// the callback). The SA_RESTART handler relies on the invariant that
// pt_regs.rcx - 6 points at the indirect JMP itself, so it can rewind
// ctx.rip and re-enter the callback.
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D, 0x06, 0x00, 0x00, 0x00]);

// Add jmp [rip + offset_to_entry_point]
trampoline_data.extend_from_slice(&[0xFF, 0x25]);
Expand All@@ -459,6 +478,20 @@ fn hook_syscalls_in_section(
"x86_64 trampoline entry",
)?);

// post_jmp: JMP rel32 back to the guest instruction following the
// original syscall. The callback returns via `jmp rcx` and lands here.
let jmp_back_base = checked_add_u64(
trampoline_base_addr,
trampoline_data.len() as u64 + 5,
"x86_64 trampoline jump-back base",
)?;
trampoline_data.push(0xE9);
trampoline_data.extend_from_slice(&rel32_bytes(
return_addr,
jmp_back_base,
"x86_64 trampoline jump-back",
)?);

// Replace original instructions with jump to trampoline
let replace_offset = usize::try_from(replace_start - section_base_addr).unwrap();
section_data[replace_offset] = 0xE9; // JMP rel32
Expand DownExpand Up@@ -538,8 +571,8 @@ fn fixup_phdr_alignment(buf: &mut [u8]) {
return;
};

if old_end > buf.len() || new_end > buf.len() {
return; // corrupt phdr table or not enough room
if new_end > buf.len() {
return; // not enough room
}

// Only relocate when the overwritten bytes are padding. Otherwise this would corrupt the file
Expand DownExpand Up@@ -933,9 +966,11 @@ fn hook_syscall_and_after(
Vec::new()
};

// Put jump back location into rcx, via lea rcx, [next instruction]
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32]
trampoline_data.extend_from_slice(&6u32.to_le_bytes());
// LEA RCX, [RIP + 6] — make RCX point at the instruction immediately
// following the indirect JMP: the start of postsyscall_bytes (or, when
// none, the unconditional JMP back to guest). The SA_RESTART handler
// relies on pt_regs.rcx - 6 pointing at the indirect JMP itself.
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D, 0x06, 0x00, 0x00, 0x00]);
// Add jmp [rip + offset_to_entry_point]
trampoline_data.extend_from_slice(&[0xFF, 0x25]);
// RIP after this instruction = trampoline_base_addr + trampoline_data.len() + 4
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Syscall rewriter improvements by wdcui · Pull Request #812 · microsoft/litebox · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 54 additions & 19 deletions litebox_syscall_rewriter/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -191,6 +191,7 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option<u64>) -> Res
trampoline_data.extend_from_slice(&trampoline.to_le_bytes());
// Patch syscalls in-place in buf
let mut skipped_addrs = Vec::new();
let mut syscall_insns_found = false;
for s in &text_sections {
let section_data = section_slice_mut(buf, s)?;
match hook_syscalls_in_section(
Expand All@@ -202,13 +203,34 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option<u64>) -> Res
trampoline_base_addr, // entry point is at offset 0 of trampoline
&mut trampoline_data,
) {
Ok(addrs) => skipped_addrs.extend(addrs),
Ok(addrs) => {
skipped_addrs.extend(addrs);
syscall_insns_found = true;
}
Err(InternalError::NoSyscallInstructionsFound) => {}
Err(InternalError::Public(e)) => return Err(e),
Err(e) => unreachable!("unexpected internal error: {e:?}"),
}
}

if !syscall_insns_found {
// No syscall instructions found. Append a header-only marker so the
// loader can distinguish "checked by rewriter, nothing to patch" from
// "never processed." The trampoline_size=0 sentinel tells the loader
// to skip trampoline mapping entirely.
// Use the original input (not `buf`) to avoid emitting the phdr
// alignment fixup that is only needed for the `object` crate parser.
let mut out = input_binary.to_vec();
let header = TrampolineHeader64 {
magic: *TRAMPOLINE_MAGIC,
file_offset: 0,
vaddr: 0,
trampoline_size: 0,
};
out.extend_from_slice(header.as_bytes());
return Ok(out);
}

// Build output: [patched ELF][padding to page boundary][trampoline code][header]
let mut out = buf.to_vec();
let remain = out.len() % 0x1000;
Expand DownExpand Up@@ -293,7 +315,9 @@ fn is_already_hooked(input_binary: &[u8], arch: Arch) -> bool {
(header.file_offset, header.vaddr, header.trampoline_size);

if trampoline_size == 0 {
return false;
// Size=0 sentinel: the rewriter processed this binary but found no
// syscall instructions. It is already hooked (nothing to do).
return true;
}
if file_offset % 0x1000 != 0 {
return false;
Expand DownExpand Up@@ -431,18 +455,13 @@ fn hook_syscalls_in_section(
trampoline_data.extend_from_slice(&presyscall_bytes);

let return_addr = inst.next_ip();
// Put jump back location into rcx.
let jmp_back_base = checked_add_u64(
trampoline_base_addr,
trampoline_data.len() as u64 + 7,
"x86_64 trampoline jump-back base",
)?;
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32]
trampoline_data.extend_from_slice(&rel32_bytes(
return_addr,
jmp_back_base,
"x86_64 trampoline jump-back",
)?);

// LEA RCX, [RIP + 6] — load RCX with the address of the in-trampoline
// `post_jmp` (the instruction immediately after the indirect JMP into
// the callback). The SA_RESTART handler relies on the invariant that
// pt_regs.rcx - 6 points at the indirect JMP itself, so it can rewind
// ctx.rip and re-enter the callback.
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D, 0x06, 0x00, 0x00, 0x00]);

// Add jmp [rip + offset_to_entry_point]
trampoline_data.extend_from_slice(&[0xFF, 0x25]);
Expand All@@ -459,6 +478,20 @@ fn hook_syscalls_in_section(
"x86_64 trampoline entry",
)?);

// post_jmp: JMP rel32 back to the guest instruction following the
// original syscall. The callback returns via `jmp rcx` and lands here.
let jmp_back_base = checked_add_u64(
trampoline_base_addr,
trampoline_data.len() as u64 + 5,
"x86_64 trampoline jump-back base",
)?;
trampoline_data.push(0xE9);
trampoline_data.extend_from_slice(&rel32_bytes(
return_addr,
jmp_back_base,
"x86_64 trampoline jump-back",
)?);

// Replace original instructions with jump to trampoline
let replace_offset = usize::try_from(replace_start - section_base_addr).unwrap();
section_data[replace_offset] = 0xE9; // JMP rel32
Expand DownExpand Up@@ -538,8 +571,8 @@ fn fixup_phdr_alignment(buf: &mut [u8]) {
return;
};

if old_end > buf.len() || new_end > buf.len() {
return; // corrupt phdr table or not enough room
if new_end > buf.len() {
return; // not enough room
}

// Only relocate when the overwritten bytes are padding. Otherwise this would corrupt the file
Expand DownExpand Up@@ -933,9 +966,11 @@ fn hook_syscall_and_after(
Vec::new()
};

// Put jump back location into rcx, via lea rcx, [next instruction]
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32]
trampoline_data.extend_from_slice(&6u32.to_le_bytes());
// LEA RCX, [RIP + 6] — make RCX point at the instruction immediately
// following the indirect JMP: the start of postsyscall_bytes (or, when
// none, the unconditional JMP back to guest). The SA_RESTART handler
// relies on pt_regs.rcx - 6 pointing at the indirect JMP itself.
trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D, 0x06, 0x00, 0x00, 0x00]);
// Add jmp [rip + offset_to_entry_point]
trampoline_data.extend_from_slice(&[0xFF, 0x25]);
// RIP after this instruction = trampoline_base_addr + trampoline_data.len() + 4
Expand Down
Loading
Loading