@@ -6,56 +6,33 @@ PoC by Shiz, bugfixing and 64-bit version by PoroCYon.
66
77## Dependencies
88
9- * A C compiler (preferably GCC), GNU ld, binutils, GNU make, ...
9+ * GCC (not clang, as the latter doesn't support ` nolto-rel ` output), GNU ld,
10+ binutils, GNU make, ...
1011* nasm 2.13 or newer
11- * scanelf from pax-utils
12+ * ` scanelf ` from ` pax-utils `
1213* Python 3
1314
1415## Usage
1516
17+ *** NOTE*** : Your entrypoint (` _start ` ) *** must*** be in a section called
18+ ` .text.startup._start ` ! Otherwise, the linker script will fail silently, and
19+ the smol startup/symbol resolving code will jump to an undefined location.
20+
1621``` sh
17- ./smol.py -lfoo -lbar input.o... smol-output.asm
18- nasm -I src/ [-Doption ...] -o nasm-output.o smol-output.asm
19- ld -T ld/link.ld --oformat=binary -o output.elf nasm-output.o input.o...
20- # or cc -T ld/link.ld -Wl,--oformat=binary -o output.elf nasm-output.o input.o...
22+ ./smold.py --use_interp --align_stack [--opts...] -lfoo -lbar input.o... output.elf
2123```
2224
23- * ` USE_INTERP ` : Include an interp segment in the output ELF file. If not, the
24- dynamic linker ** must** be invoked * explicitely* ! (You probably want to
25- enable this.) Costs the size of a phdr plus the size of the interp string.
26- * ` ALIGN_STACK ` : * 64-bit only* : realign the stack so that SSE instructions
27- won't segfault. Costs 1 byte.
28- * ` USE_NX ` : Don't use ` RWE ` segments at all. Not very well tested. Costs the
29- size of 1 phdr, plus some extra stuff on ` i386 ` . Don't forget to pass ` -n `
30- to ` smol.py ` as well.
31- * ` USE_DL_FINI ` : keep track of the ` _dl_fini ` function and pass it to your
32- ` _start ` . Costs 2 bytes, plus maybe a few more depending on how it's passed
33- to ` __libc_start_main ` .
34- * ` USE_DT_DEBUG ` : retrieve the ` struct link_map ` from the ` r_debug ` linker
35- data (which is placed at ` DT_DEBUG ` at startup) instead of exploiting data
36- leakage from ` _dt_start_user ` . Might be more compatible and compressable, but
37- strictly worse size-wise by 10 (i386) or 3 (x86_64) bytes.
38- * ` SKIP_ENTRIES ` : skip the first two entries of the ` struct link_map ` , which
39- represent the main binary and the vDSO. Costs around 5 bytes.
40- * ` USE_DNLOAD_LOADER ` : use the symbol loading mechanism as used in dnload (i.e.
41- traverse the symtab of the imported libraries). Slightly larger, but probably
42- better compressable and more compatible with other libcs and future versions
43- of glibc.
44- * ` NO_START_ARG ` : * don't* pass the stack pointer to ` _start ` as the first arg.
45- Will make it unable to read argc/argv/environ, but gives you 3 bytes.
46- * ` SKIP_ZERO_VALUE ` : skip a ` Sym ` with a ` st_value ` field of ` 0 ` . If this isn't
47- enabled, weak symbols etc. might be imported instead of the real ones,
48- causing breakage. Many libraries don't have weak symbols at all, though.
49- Costs 4 (i386) or 5 (x86_64) bytes.
50-
5125```
52- usage: smol.py [-h] [-m TARGET] [-l LIB] [-L DIR] [--nasm NASM] [--cc CC]
53- [--scanelf SCANELF] [--readelf READELF]
54- input [input ...] output
26+ usage: smold.py [-h] [-m TARGET] [-l LIB] [-L DIR] [-s] [-n] [-d] [-fuse-interp] [-falign-stack] [-fuse-nx]
27+ [-fuse-dnload-loader] [-fskip-zero-value] [-fuse-dt-debug] [-fuse-dl-fini] [-fskip-entries]
28+ [-fno-start-arg] [-funsafe-dynamic] [--nasm NASM] [--cc CC] [--scanelf SCANELF] [--readelf READELF]
29+ [--cflags CFLAGS] [--asflags ASFLAGS] [--ldflags LDFLAGS] [--smolrt SMOLRT] [--smolld SMOLLD]
30+ [--verbose] [--keeptmp]
31+ input [input ...] output
5532
5633positional arguments:
5734 input input object file
58- output output nasm file
35+ output output binary
5936
6037optional arguments:
6138 -h, --help show this help message and exit
@@ -64,14 +41,45 @@ optional arguments:
6441 -l LIB, --library LIB
6542 libraries to link against
6643 -L DIR, --libdir DIR directories to search libraries in
44+ -s, --hash16 Use 16-bit (BSD) hashes instead of 32-bit djb2 hashes. Implies -fuse-dnload-loader
45+ -n, --nx Use NX (i.e. don't use RWE pages). Costs the size of one phdr, plus some extra bytes on
46+ i386.
47+ -d, --det Make the order of imports deterministic (default: just use whatever binutils throws at us)
48+ -fuse-interp Include a program interpreter header (PT_INTERP). If not enabled, ld.so has to be invoked
49+ manually by the end user.
50+ -falign-stack Align the stack before running user code (_start). If not enabled, this has to be done
51+ manually. Costs 1 byte.
52+ -fuse-nx Don't use one big RWE segment, but use separate RW and RE ones. Use this to keep strict
53+ kernels (PaX/grsec) happy. Costs at least the size of one program header entry.
54+ -fuse-dnload-loader Use a dnload-style loader for resolving symbols, which doesn't depend on
55+ nonstandard/undocumented ELF and ld.so features, but is slightly larger. If not enabled, a
56+ smaller custom loader is used which assumes glibc.
57+ -fskip-zero-value Skip an ELF symbol with a zero address (a weak symbol) when parsing libraries at runtime.
58+ Try enabling this if you're experiencing sudden breakage. However, many libraries don't use
59+ weak symbols, so this doesn't often pose a problem. Costs ~5 bytes.
60+ -fuse-dt-debug Use the DT_DEBUG Dyn header to access the link_map, which doesn't depend on
61+ nonstandard/undocumented ELF and ld.so features. If not enabled, the link_map is accessed
62+ using data leaked to the entrypoint by ld.so, which assumes glibc. Costs ~10 bytes.
63+ -fuse-dl-fini Pass _dl_fini to the user entrypoint, which should be done to properly comply with all
64+ standards, but is very often not needed at all. Costs 2 bytes.
65+ -fskip-entries Skip the first two entries in the link map (resp. ld.so and the vDSO). Speeds up symbol
66+ resolving, but costs ~5 bytes.
67+ -fno-start-arg Don't pass a pointer to argc/argv/envp to the entrypoint using the standard calling
68+ convention. This means you need to read these yourself in assembly if you want to use them!
69+ (envp is a preprequisite for X11, because it needs $DISPLAY.) Frees 3 bytes.
70+ -funsafe-dynamic Don't end the ELF Dyn table with a DT_NULL entry. This might cause ld.so to interpret the
71+ entire binary as the Dyn table, so only enable this if you're sure this won't break things!
6772 --nasm NASM which nasm binary to use
68- --cc CC which cc binary to use
73+ --cc CC which cc binary to use (MUST BE GCC!)
6974 --scanelf SCANELF which scanelf binary to use
7075 --readelf READELF which readelf binary to use
71- -n, --nx Use NX (i.e. don't use RWE pages). Costs the size of
72- one phdr, plus some extra bytes on i386. Don't forget
73- to pass -DUSE_NX to the assembly loader as well!
74-
76+ --cflags CFLAGS Flags to pass to the C compiler for the relinking step
77+ --asflags ASFLAGS Flags to pass to the assembler when creating the ELF header and runtime startup code
78+ --ldflags LDFLAGS Flags to pass to the linker for the final linking step
79+ --smolrt SMOLRT Directory containing the smol runtime sources
80+ --smolld SMOLLD Directory containing the smol linker scripts
81+ --verbose Be verbose about what happens and which subcommands are invoked
82+ --keeptmp Keep temp files (only useful for debugging)
7583```
7684
7785A minimal crt (and ` _start ` funcion) are provided in case you want to use ` main ` .
@@ -83,9 +91,6 @@ imported by a `smol`-ified binary. This can thus be used to detect user mistakes
8391during dynamic linking. (Think of it as an equivalent of ` ldd ` , except that it
8492also checks whether the imported functions are present as well.)
8593
86- *** NOTE*** : ` smoldd.py ` currently doesn't support 64-bit binaries anymore, as
87- there's currently no (good) way of retrieving the symbol hash table anymore.
88-
8994## Internal workings
9095
9196` smol.py ` inspects the input object files for needed library files and symbols.
@@ -99,7 +104,7 @@ works for glibc): on both i386 and x86_64, the linker startup code
99104(` _dl_start_user ` ) leaks the global ` struct link_map ` to the user code:
100105on i386, a pointer to it is passed directly through ` eax ` :
101106
102- ``` s
107+ ``` asm
103108# (eax, edx, ecx, esi) = (_dl_loaded, argc, argv, envp)
104109movl _rtld_local@GOTOFF(%ebx), %eax
105110## [ boring stuff... ]
0 commit comments