1use std::collections::HashMap;
11
12pub const PT_LOAD: u32 = 1;
13pub const PT_DYNAMIC: u32 = 2;
14pub const PT_PHDR: u32 = 6;
15const DT_NULL: u64 = 0;
16const DT_NEEDED: u64 = 1;
17const DT_STRTAB: u64 = 5;
18const DT_STRSZ: u64 = 10;
19const DT_SONAME: u64 = 14;
20const DT_VERNEED: u64 = 0x6fff_fffe;
21const DT_VERNEEDNUM: u64 = 0x6fff_ffff;
22const SHT_STRTAB: u32 = 3;
23
24#[derive(Debug, Clone, Copy)]
25pub struct Phdr {
26 pub p_type: u32,
27 pub flags: u32,
28 pub offset: u64,
29 pub vaddr: u64,
30 pub paddr: u64,
31 pub filesz: u64,
32 pub memsz: u64,
33 pub align: u64,
34}
35
36#[derive(Debug, Clone)]
37pub struct Info {
38 pub is64: bool,
39 pub machine: u16,
40 pub soname: Option<String>,
41 pub needed: Vec<String>,
42 pub phdrs: Vec<Phdr>,
43}
44
45impl Info {
46 pub fn min_load_align(&self) -> u64 {
48 self.phdrs.iter().filter(|p| p.p_type == PT_LOAD).map(|p| p.align).min().unwrap_or(0)
49 }
50
51 pub fn abi(&self) -> &'static str {
53 match (self.machine, self.is64) {
54 (183, _) => "arm64-v8a",
55 (40, _) => "armeabi-v7a",
56 (62, _) => "x86_64",
57 (3, _) => "x86",
58 (243, _) => "riscv64",
59 _ => "unknown",
60 }
61 }
62}
63
64struct Elf<'a> {
65 d: &'a [u8],
66 is64: bool,
67}
68
69type R<T> = Result<T, String>;
70
71impl<'a> Elf<'a> {
72 fn new(d: &'a [u8]) -> R<Self> {
73 if d.len() < 52 || &d[..4] != b"\x7fELF" {
74 return Err("not an ELF file".into());
75 }
76 if d[5] != 1 {
77 return Err("big-endian ELF is not supported".into());
78 }
79 let is64 = match d[4] {
80 1 => false,
81 2 => true,
82 c => return Err(format!("bad ELF class {c}")),
83 };
84 if is64 && d.len() < 64 {
85 return Err("truncated ELF header".into());
86 }
87 Ok(Elf { d, is64 })
88 }
89 fn u16(&self, o: u64) -> R<u16> {
90 let o = o as usize;
91 self.d
92 .get(o..o + 2)
93 .map(|b| u16::from_le_bytes(b.try_into().unwrap()))
94 .ok_or_else(|| format!("read past end at {o}"))
95 }
96 fn u32(&self, o: u64) -> R<u32> {
97 let o = o as usize;
98 self.d
99 .get(o..o + 4)
100 .map(|b| u32::from_le_bytes(b.try_into().unwrap()))
101 .ok_or_else(|| format!("read past end at {o}"))
102 }
103 fn u64(&self, o: u64) -> R<u64> {
104 let o = o as usize;
105 self.d
106 .get(o..o + 8)
107 .map(|b| u64::from_le_bytes(b.try_into().unwrap()))
108 .ok_or_else(|| format!("read past end at {o}"))
109 }
110 fn word(&self, o: u64) -> R<u64> {
112 if self.is64 {
113 self.u64(o)
114 } else {
115 self.u32(o).map(u64::from)
116 }
117 }
118 fn e_type(&self) -> R<u16> {
119 self.u16(16)
120 }
121 fn phoff(&self) -> R<u64> {
122 if self.is64 {
123 self.u64(32)
124 } else {
125 self.u32(28).map(u64::from)
126 }
127 }
128 fn shoff(&self) -> R<u64> {
129 if self.is64 {
130 self.u64(40)
131 } else {
132 self.u32(32).map(u64::from)
133 }
134 }
135 fn phentsize(&self) -> R<u16> {
136 self.u16(if self.is64 { 54 } else { 42 })
137 }
138 fn phnum(&self) -> R<u16> {
139 self.u16(if self.is64 { 56 } else { 44 })
140 }
141 fn shentsize(&self) -> R<u16> {
142 self.u16(if self.is64 { 58 } else { 46 })
143 }
144 fn shnum(&self) -> R<u16> {
145 self.u16(if self.is64 { 60 } else { 48 })
146 }
147 fn phdr(&self, i: u64) -> R<Phdr> {
148 let o = self.phoff()? + i * self.phentsize()? as u64;
149 Ok(if self.is64 {
150 Phdr {
151 p_type: self.u32(o)?,
152 flags: self.u32(o + 4)?,
153 offset: self.u64(o + 8)?,
154 vaddr: self.u64(o + 16)?,
155 paddr: self.u64(o + 24)?,
156 filesz: self.u64(o + 32)?,
157 memsz: self.u64(o + 40)?,
158 align: self.u64(o + 48)?,
159 }
160 } else {
161 Phdr {
162 p_type: self.u32(o)?,
163 offset: self.u32(o + 4)? as u64,
164 vaddr: self.u32(o + 8)? as u64,
165 paddr: self.u32(o + 12)? as u64,
166 filesz: self.u32(o + 16)? as u64,
167 memsz: self.u32(o + 20)? as u64,
168 flags: self.u32(o + 24)?,
169 align: self.u32(o + 28)? as u64,
170 }
171 })
172 }
173 fn phdrs(&self) -> R<Vec<Phdr>> {
174 (0..self.phnum()? as u64).map(|i| self.phdr(i)).collect()
175 }
176 fn vaddr_to_off(&self, phdrs: &[Phdr], v: u64) -> R<u64> {
177 phdrs
178 .iter()
179 .find(|p| p.p_type == PT_LOAD && v >= p.vaddr && v < p.vaddr + p.filesz)
180 .map(|p| v - p.vaddr + p.offset)
181 .ok_or_else(|| format!("address {v:#x} is not in a loaded segment"))
182 }
183 fn dyn_entries(&self, phdrs: &[Phdr]) -> R<Vec<(u64, u64, u64)>> {
184 let dynp = phdrs.iter().find(|p| p.p_type == PT_DYNAMIC).ok_or("no PT_DYNAMIC (not a shared object?)")?;
185 let es = if self.is64 { 16 } else { 8 };
186 let mut v = vec![];
187 let mut o = dynp.offset;
188 while o + es <= dynp.offset + dynp.filesz {
189 let (tag, val) = (self.word(o)?, self.word(o + es / 2)?);
190 v.push((o, tag, val));
191 if tag == DT_NULL {
192 break;
193 }
194 o += es;
195 }
196 Ok(v)
197 }
198 fn cstr(&self, o: u64) -> R<String> {
199 let s = &self.d.get(o as usize..).ok_or("string past end")?;
200 let n = s.iter().position(|&c| c == 0).ok_or("unterminated string")?;
201 String::from_utf8(s[..n].to_vec()).map_err(|_| "non-UTF-8 string".into())
202 }
203}
204
205pub fn info(d: &[u8]) -> R<Info> {
206 let e = Elf::new(d)?;
207 let phdrs = e.phdrs()?;
208 let dynv = e.dyn_entries(&phdrs)?;
209 let strtab = dynv.iter().find(|x| x.1 == DT_STRTAB).ok_or("no DT_STRTAB")?.2;
210 let stroff = e.vaddr_to_off(&phdrs, strtab)?;
211 let mut soname = None;
212 let mut needed = vec![];
213 for &(_, tag, val) in &dynv {
214 match tag {
215 DT_SONAME => soname = Some(e.cstr(stroff + val)?),
216 DT_NEEDED => needed.push(e.cstr(stroff + val)?),
217 _ => {}
218 }
219 }
220 Ok(Info { is64: e.is64, machine: e.u16(18)?, soname, needed, phdrs })
221}
222
223fn put(out: &mut [u8], o: u64, v: u64, width: usize) {
224 let o = o as usize;
225 match width {
226 2 => out[o..o + 2].copy_from_slice(&(v as u16).to_le_bytes()),
227 4 => out[o..o + 4].copy_from_slice(&(v as u32).to_le_bytes()),
228 _ => out[o..o + 8].copy_from_slice(&v.to_le_bytes()),
229 }
230}
231
232fn encode_phdr(p: &Phdr, is64: bool) -> Vec<u8> {
233 let mut b = vec![];
234 if is64 {
235 b.extend(p.p_type.to_le_bytes());
236 b.extend(p.flags.to_le_bytes());
237 for v in [p.offset, p.vaddr, p.paddr, p.filesz, p.memsz, p.align] {
238 b.extend(v.to_le_bytes());
239 }
240 } else {
241 b.extend(p.p_type.to_le_bytes());
242 for v in [p.offset, p.vaddr, p.paddr, p.filesz, p.memsz] {
243 b.extend((v as u32).to_le_bytes());
244 }
245 b.extend(p.flags.to_le_bytes());
246 b.extend((p.align as u32).to_le_bytes());
247 }
248 b
249}
250
251fn round_up(v: u64, a: u64) -> u64 {
252 v.div_ceil(a) * a
253}
254
255pub fn rewrite(d: &[u8], soname: Option<&str>, needed: &HashMap<String, String>) -> R<Vec<u8>> {
258 let e = Elf::new(d)?;
259 if e.e_type()? != 3 {
260 return Err("not a shared object (ET_DYN)".into());
261 }
262 let phdrs = e.phdrs()?;
263 let dynv = e.dyn_entries(&phdrs)?;
264 let strtab = dynv.iter().find(|x| x.1 == DT_STRTAB).ok_or("no DT_STRTAB")?.2;
265 let strsz = dynv.iter().find(|x| x.1 == DT_STRSZ).ok_or("no DT_STRSZ")?.2;
266 let stroff = e.vaddr_to_off(&phdrs, strtab)?;
267 let old_str = d.get(stroff as usize..(stroff + strsz) as usize).ok_or("dynstr out of range")?;
268
269 let mut new_str = old_str.to_vec();
271 let mut added: HashMap<String, u64> = HashMap::new();
272 let mut intern = |s: &str, buf: &mut Vec<u8>| -> u64 {
273 *added.entry(s.to_string()).or_insert_with(|| {
274 let o = buf.len() as u64;
275 buf.extend_from_slice(s.as_bytes());
276 buf.push(0);
277 o
278 })
279 };
280 let mut dyn_edits: Vec<(u64, u64)> = vec![]; let half = if e.is64 { 8 } else { 4 };
282 for &(o, tag, val) in &dynv {
283 if tag != DT_SONAME && tag != DT_NEEDED {
284 continue;
285 }
286 let cur = e.cstr(stroff + val)?;
287 let repl = match tag {
288 DT_SONAME => soname.map(str::to_string),
289 DT_NEEDED => needed.get(&cur).cloned(),
290 _ => None,
291 };
292 if let Some(r) = repl {
293 if r != cur {
294 dyn_edits.push((o + half, intern(&r, &mut new_str)));
295 }
296 }
297 }
298 let has_soname = dynv.iter().any(|x| x.1 == DT_SONAME);
299 if soname.is_some() && !has_soname {
300 return Err("library has no DT_SONAME entry to rewrite (relink with -Wl,-soname)".into());
301 }
302 let mut vn_edits: Vec<(u64, u64)> = vec![];
304 if let (Some(vn), Some(cnt)) =
305 (dynv.iter().find(|x| x.1 == DT_VERNEED).map(|x| x.2), dynv.iter().find(|x| x.1 == DT_VERNEEDNUM).map(|x| x.2))
306 {
307 let mut o = e.vaddr_to_off(&phdrs, vn)?;
308 for _ in 0..cnt {
309 let file = e.cstr(stroff + e.u32(o + 4)? as u64)?;
310 if let Some(r) = needed.get(&file) {
311 vn_edits.push((o + 4, intern(r, &mut new_str)));
312 }
313 let next = e.u32(o + 12)? as u64;
314 if next == 0 {
315 break;
316 }
317 o += next;
318 }
319 }
320 if dyn_edits.is_empty() && vn_edits.is_empty() {
321 return Ok(d.to_vec());
322 }
323
324 let loads: Vec<&Phdr> = phdrs.iter().filter(|p| p.p_type == PT_LOAD).collect();
326 let align = loads.iter().map(|p| p.align).max().unwrap_or(4096).max(4096);
327 let vend = loads.iter().map(|p| p.vaddr + p.memsz).max().unwrap_or(0);
328 let new_off = round_up(d.len() as u64, align);
329 let new_vaddr = round_up(vend, align);
330 let has_phdr = phdrs.iter().any(|p| p.p_type == PT_PHDR);
331 let entsize = e.phentsize()? as u64;
332 let count = phdrs.len() as u64 + 1 + u64::from(!has_phdr);
333 let ph_size = count * entsize;
334 let str_rel = round_up(ph_size, 8);
335 let seg_size = str_rel + new_str.len() as u64;
336
337 let mut newph: Vec<Phdr> = vec![];
338 let phdr_entry = Phdr {
339 p_type: PT_PHDR,
340 flags: 4,
341 offset: new_off,
342 vaddr: new_vaddr,
343 paddr: new_vaddr,
344 filesz: ph_size,
345 memsz: ph_size,
346 align: 8,
347 };
348 if !has_phdr {
349 newph.push(phdr_entry);
350 }
351 let last_load = phdrs.iter().rposition(|p| p.p_type == PT_LOAD).ok_or("no PT_LOAD")?;
352 for (i, p) in phdrs.iter().enumerate() {
353 newph.push(if p.p_type == PT_PHDR { phdr_entry } else { *p });
354 if i == last_load {
355 newph.push(Phdr {
356 p_type: PT_LOAD,
357 flags: 4,
358 offset: new_off,
359 vaddr: new_vaddr,
360 paddr: new_vaddr,
361 filesz: seg_size,
362 memsz: seg_size,
363 align,
364 });
365 }
366 }
367
368 let mut out = d.to_vec();
369 out.resize(new_off as usize, 0);
370 for p in &newph {
371 out.extend(encode_phdr(p, e.is64));
372 }
373 out.resize((new_off + str_rel) as usize, 0);
374 out.extend_from_slice(&new_str);
375
376 if e.is64 {
378 put(&mut out, 32, new_off, 8);
379 put(&mut out, 56, count, 2);
380 } else {
381 put(&mut out, 28, new_off, 4);
382 put(&mut out, 44, count, 2);
383 }
384 let w = if e.is64 { 8 } else { 4 };
385 for &(o, tag, _) in &dynv {
386 if tag == DT_STRTAB {
387 put(&mut out, o + half, new_vaddr + str_rel, w);
388 } else if tag == DT_STRSZ {
389 put(&mut out, o + half, new_str.len() as u64, w);
390 }
391 }
392 for (o, v) in dyn_edits {
393 put(&mut out, o, v, w);
394 }
395 for (o, v) in vn_edits {
396 put(&mut out, o, v, 4);
397 }
398 let shoff = e.shoff()?;
400 if shoff != 0 {
401 let es = e.shentsize()? as u64;
402 for i in 0..e.shnum()? as u64 {
403 let o = shoff + i * es;
404 let (ty, off) =
405 if e.is64 { (e.u32(o + 4)?, e.u64(o + 24)?) } else { (e.u32(o + 4)?, e.u32(o + 16)? as u64) };
406 if ty == SHT_STRTAB && off == stroff {
407 if e.is64 {
408 put(&mut out, o + 16, new_vaddr + str_rel, 8);
409 put(&mut out, o + 24, new_off + str_rel, 8);
410 put(&mut out, o + 32, new_str.len() as u64, 8);
411 } else {
412 put(&mut out, o + 12, new_vaddr + str_rel, 4);
413 put(&mut out, o + 16, new_off + str_rel, 4);
414 put(&mut out, o + 20, new_str.len() as u64, 4);
415 }
416 }
417 }
418 }
419 Ok(out)
420}
421
422#[cfg(test)]
423mod tests {
424 use super::*;
425
426 #[test]
427 fn rejects_non_elf() {
428 assert!(info(b"hello").is_err());
429 assert!(rewrite(&[0u8; 100], None, &HashMap::new()).is_err());
430 }
431}