diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9af949b5537af7..9c409cf24fddf0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -788,6 +788,7 @@ /tools/bazel* @DataDog/agent-build /tools/ci @DataDog/agent-devx /tools/ebpf/ @DataDog/ebpf-platform +/tools/elf-deduplicator/ @DataDog/agent-security /tools/gdb/ @DataDog/agent-runtimes /tools/go-update/ @DataDog/agent-runtimes /tools/NamedPipeCmd/ @DataDog/windows-products diff --git a/tools/elf-deduplicator/.gitignore b/tools/elf-deduplicator/.gitignore new file mode 100644 index 00000000000000..361bbda5d98ad5 --- /dev/null +++ b/tools/elf-deduplicator/.gitignore @@ -0,0 +1,5 @@ +# Rust build artifacts +/target/ + +# Object files +*.o \ No newline at end of file diff --git a/tools/elf-deduplicator/Cargo.lock b/tools/elf-deduplicator/Cargo.lock new file mode 100644 index 00000000000000..af756a39c9033e --- /dev/null +++ b/tools/elf-deduplicator/Cargo.lock @@ -0,0 +1,428 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "elf-deduplicator" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "goblin", + "indexmap", + "object", + "sha2", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "goblin" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" +dependencies = [ + "log", + "plain", + "scroll", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "flate2", + "memchr", + "ruzstd", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ruzstd" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad02996bfc73da3e301efe90b1837be9ed8f4a462b6ed410aa35d00381de89f" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/tools/elf-deduplicator/Cargo.toml b/tools/elf-deduplicator/Cargo.toml new file mode 100644 index 00000000000000..be1bc402f52ee8 --- /dev/null +++ b/tools/elf-deduplicator/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "elf-deduplicator" +version = "0.1.0" +edition = "2024" + +[dependencies] +object = "0.36" +goblin = "0.8" +clap = { version = "4.0", features = ["derive"] } +anyhow = "1.0" +sha2 = "0.10" +indexmap = "2.0" diff --git a/tools/elf-deduplicator/README.md b/tools/elf-deduplicator/README.md new file mode 100644 index 00000000000000..a338e73eada9b6 --- /dev/null +++ b/tools/elf-deduplicator/README.md @@ -0,0 +1,79 @@ +# ELF Deduplicator + +A Rust tool for deduplicating sections in ELF files by sharing common data blocks. Particularly useful for eBPF programs that often contain many sections with identical data but different metadata. + +## Usage + +```bash +elf-deduplicator --input --output [--verbose] +``` + +### Options + +- `-i, --input `: Path to the input ELF file +- `-o, --output `: Path for the output deduplicated ELF file +- `-v, --verbose`: Enable verbose output showing deduplication progress +- `-h, --help`: Show help information + +## Example + +```bash +# Basic usage +./target/debug/elf-deduplicator -i input.o -o output.o + +# With verbose output +./target/debug/elf-deduplicator -i input.o -o output.o --verbose +``` + +## How it works + +1. **Parse ELF**: Uses the `object` and `goblin` crates to parse the input ELF file +2. **Extract sections**: Extracts all section data and metadata +3. **Deduplicate**: Uses SHA-256 hashing to identify sections with identical data +4. **Reconstruct**: Rebuilds the ELF file with shared data blocks, updating section headers to point to the deduplicated data + +## Test Results + +Testing with Datadog Agent's runtime-security eBPF files: + +### runtime-security.o +- **Original size**: 18,820,192 bytes +- **Deduplicated size**: 17,950,408 bytes +- **Savings**: 869,784 bytes (4.62%) + +### runtime-security-fentry.o +- **Original size**: 16,177,904 bytes +- **Deduplicated size**: 15,344,328 bytes +- **Savings**: 833,576 bytes (5.15%) + +The tool successfully identifies and deduplicates many sections, particularly those corresponding to similar syscall handlers (e.g., `sys_chmod` vs `compat_sys_chmod`) and their associated relocation sections. + +## Dependencies + +- `object`: ELF parsing and manipulation +- `goblin`: Additional ELF format support +- `clap`: Command-line argument parsing +- `anyhow`: Error handling +- `sha2`: SHA-256 hashing for deduplication +- `indexmap`: Ordered collections + +## Building + +```bash +cargo build --release +``` + +## Features + +- ✅ Deduplicates identical section data +- ✅ Preserves section metadata (names, addresses, alignment, flags) +- ✅ Maintains valid ELF structure +- ✅ Command-line interface with verbose output +- ✅ SHA-256 based content hashing for reliable deduplication +- ✅ Tested with eBPF ELF files + +## Limitations + +- Currently handles ELF reconstruction in a simplified manner +- Assumes little-endian byte order for section header writing +- Basic error handling - more robust validation could be added \ No newline at end of file diff --git a/tools/elf-deduplicator/src/main.rs b/tools/elf-deduplicator/src/main.rs new file mode 100644 index 00000000000000..2c6c325a0153ea --- /dev/null +++ b/tools/elf-deduplicator/src/main.rs @@ -0,0 +1,216 @@ +use anyhow::{Context, Result}; +use clap::Parser; +use object::{Object, ObjectSection}; +use sha2::{Digest, Sha256}; +use std::collections::HashMap; +use std::fs; +use std::path::PathBuf; + +#[derive(Parser)] +#[command(name = "elf-deduplicator")] +#[command(about = "Deduplicate sections in ELF files by sharing common data blocks")] +struct Args { + #[arg(short, long)] + input: PathBuf, + + #[arg(short, long)] + output: PathBuf, + + #[arg(short, long, default_value = "false")] + verbose: bool, +} + +#[derive(Clone, Debug)] +struct SectionInfo { + name: String, + data: Vec, +} + +#[derive(Clone, Debug)] +struct DataBlock { + data: Vec, + sections: Vec, +} + +fn main() -> Result<()> { + let args = Args::parse(); + + let input_data = fs::read(&args.input) + .with_context(|| format!("Failed to read input file: {:?}", args.input))?; + + let elf_file = object::File::parse(&input_data[..]) + .with_context(|| "Failed to parse ELF file")?; + + // Only support 64-bit ELF files (including eBPF which is 64-bit) + if !elf_file.is_64() { + return Err(anyhow::anyhow!( + "Only 64-bit ELF files are supported. This appears to be a 32-bit ELF file." + )); + } + + if args.verbose { + println!("Processing 64-bit ELF file: {:?}", args.input); + println!("Architecture: {:?}", elf_file.architecture()); + println!("Endianness: {:?}", elf_file.endianness()); + } + + let sections = extract_sections(&elf_file)?; + let (deduplicated_blocks, section_to_block) = deduplicate_sections(sections, args.verbose)?; + + let output_data = reconstruct_elf(&input_data, &deduplicated_blocks, §ion_to_block)?; + + fs::write(&args.output, output_data) + .with_context(|| format!("Failed to write output file: {:?}", args.output))?; + + if args.verbose { + println!("Successfully wrote deduplicated ELF to: {:?}", args.output); + } + + Ok(()) +} + +fn extract_sections(elf_file: &object::File) -> Result> { + let mut sections = Vec::new(); + + for section in elf_file.sections() { + let name = section.name().unwrap_or("").to_string(); + let data = section.data().unwrap_or(&[]).to_vec(); + + sections.push(SectionInfo { + name, + data, + }); + } + + Ok(sections) +} + +fn deduplicate_sections(sections: Vec, verbose: bool) -> Result<(Vec, Vec)> { + let mut hash_to_block: HashMap = HashMap::new(); + let mut blocks: Vec = Vec::new(); + let mut section_to_block: Vec = Vec::new(); + + for (section_idx, section) in sections.iter().enumerate() { + let mut hasher = Sha256::new(); + hasher.update(§ion.data); + let hash = format!("{:x}", hasher.finalize()); + + if let Some(&existing_block_idx) = hash_to_block.get(&hash) { + blocks[existing_block_idx].sections.push(section_idx); + section_to_block.push(existing_block_idx); + + if verbose { + println!("Section '{}' shares data with existing block {}", + section.name, existing_block_idx); + } + } else { + let block_idx = blocks.len(); + blocks.push(DataBlock { + data: section.data.clone(), + sections: vec![section_idx], + }); + hash_to_block.insert(hash, block_idx); + section_to_block.push(block_idx); + + if verbose { + println!("Section '{}' creates new data block {}", + section.name, block_idx); + } + } + } + + if verbose { + let total_original_size: usize = sections.iter().map(|s| s.data.len()).sum(); + let total_deduplicated_size: usize = blocks.iter().map(|b| b.data.len()).sum(); + println!("Original total size: {} bytes", total_original_size); + println!("Deduplicated size: {} bytes", total_deduplicated_size); + println!("Space saved: {} bytes ({:.1}%)", + total_original_size - total_deduplicated_size, + 100.0 * (total_original_size - total_deduplicated_size) as f64 / total_original_size as f64); + } + + Ok((blocks, section_to_block)) +} + +fn reconstruct_elf( + original_data: &[u8], + blocks: &[DataBlock], + section_to_block: &[usize] +) -> Result> { + use goblin::elf::Elf; + use std::io::{Cursor, Write, Seek, SeekFrom}; + + let elf = Elf::parse(original_data)?; + let mut output = Cursor::new(Vec::new()); + + // Copy ELF header (64-bit only) + let header_size = 64; + output.write_all(&original_data[0..header_size])?; + + // Calculate new layout + let mut current_offset = header_size as u64; + let mut data_blocks_offset = Vec::new(); + + // Reserve space for section headers (we'll write them at the end) + let section_headers_offset = current_offset; + let section_header_size = 64; // 64-bit section headers only + current_offset += (elf.section_headers.len() * section_header_size) as u64; + + // Write data blocks and track their offsets + for (_block_idx, block) in blocks.iter().enumerate() { + // Align to 8 bytes + while current_offset % 8 != 0 { + current_offset += 1; + } + data_blocks_offset.push(current_offset); + current_offset += block.data.len() as u64; + } + + // Resize output buffer + output.get_mut().resize(current_offset as usize, 0); + + // Write data blocks + for (block_idx, block) in blocks.iter().enumerate() { + let offset = data_blocks_offset[block_idx]; + output.seek(SeekFrom::Start(offset))?; + output.write_all(&block.data)?; + } + + // Update section headers to point to deduplicated data + output.seek(SeekFrom::Start(section_headers_offset))?; + for (section_idx, section_header) in elf.section_headers.iter().enumerate() { + if section_idx < section_to_block.len() { + let block_idx = section_to_block[section_idx]; + let data_offset = data_blocks_offset[block_idx]; + + // Create updated section header + let mut updated_header = section_header.clone(); + updated_header.sh_offset = data_offset; + + // Write 64-bit section header + output.write_all(&updated_header.sh_name.to_le_bytes())?; + output.write_all(&updated_header.sh_type.to_le_bytes())?; + output.write_all(&updated_header.sh_flags.to_le_bytes())?; + output.write_all(&updated_header.sh_addr.to_le_bytes())?; + output.write_all(&updated_header.sh_offset.to_le_bytes())?; + output.write_all(&updated_header.sh_size.to_le_bytes())?; + output.write_all(&updated_header.sh_link.to_le_bytes())?; + output.write_all(&updated_header.sh_info.to_le_bytes())?; + output.write_all(&updated_header.sh_addralign.to_le_bytes())?; + output.write_all(&updated_header.sh_entsize.to_le_bytes())?; + } else { + // Copy original section header + let start = section_headers_offset as usize + section_idx * section_header_size; + let end = start + section_header_size; + output.write_all(&original_data[start..end])?; + } + } + + // Update ELF header to point to our section headers + let mut result = output.into_inner(); + // Update e_shoff field (offset 40 in 64-bit ELF header) + let shoff_bytes = section_headers_offset.to_le_bytes(); + result[40..48].copy_from_slice(&shoff_bytes); + + Ok(result) +} \ No newline at end of file