Preeny helps you pwn noobs by making it easier to interact with services locally.
It disables fork(), rand(), and alarm() and, if you want, can convert a server application to a console one using clever/hackish tricks, and can even patch binaries!
Preeny has the following modules:
| Name | Summary |
|---|---|
| dealarm | Disables alarm() |
| defork | Disables fork() |
| deptrace | Disables ptrace() |
| derand | Disables rand() and random() |
| desigact | Disables sigaction() |
| desock | Channels socket communication to the console |
| desock_dup | Channels socket communication to the console (simpler method) |
| ensock | The opposite of desock -- like an LD_PRELOAD version of socat! |
| desrand | Does tricky things with srand() to control randomness. |
| detime | Makes time() always return the same value. |
| desleep | Makes sleep() and usleep() do nothing. |
| mallocwatch | When ltrace is inconvenient, mallocwatch provides info on heap operations. |
| writeout | Some binaries write() to fd 0, expecting it to be a two-way socket. This makes that work (by redirecting to fd 1). |
| patch | Patches programs at load time. |
| startstop | Sends SIGSTOP to itself on startup, to suspend the process. |
| crazyrealloc | ensures that whatever is being reallocated is always moved to a new location in memory, thus free()ing the old. |
| deuid | Change the UID and effective UID of a process |
| eofkiller | Exit on EOF on several read functions |
| getcanary | Dumps the canary on program startup (x86 and amd64 only at the moment). |
| setcanary | Overwrites the canary with a user-provided one on program startup (amd64-only at the moment). |
| setstdin | Sets user defined STDIN data instead of real one, overriding read, fread, fgetc, getc and getchar calls. Read here for more info |
| nowrite | Forces open() to open files in readonly mode. Downgrading from readwrite or writeonly mode, and taking care of append, mktemp and other write-related flags as well |
| pdeathsig | Makes a process, and everything it spawns, die when its parent dies. Read here for more info |
preeny's patch functionality uses libini_config to read .ini files.
- On debian-based distros, you can install
libini-config-dev. - On Arch-based distros, you can install
ding-libs. - On Fedora-based distros, you can install
libini_config-devel.
Also deexec uses seccomp to setup a filter to blacklist execve like calls.
- On debian-based distros, you can install
libseccomp-dev. - On Arch-based distros, you can install
libseccomp. - On Fedora-based distros, you can install
libseccomp-devel.
If you're not running a debian, Arch, or Fedora based distro, you've brought the pain upon yourself.
You can build preeny by doing:
make
It'll create a directory named after the OS and architecture type, then put the libraries there.
If you need to build 32-bit x86 preeny libs on a 64-bit x86 host, you can do:
make ARCH=i386
Alternatively, if you want to utilize a cross-compiler, pass the CC variable to make. For example:
make -i CC=mips-malta-linux-gnu-gcc
Because some modules fail in cross-complilation, it's recommended to use make -i.
You can also build the project with cmake. Look at the cmake-build-*.sh scripts for example on how.
Let's say that you have an application that you want to interact with on the commandline, but it a) forks, b) sets an alarm which makes it hard to take your time studying its behavior, and c) demands to be connected to even if you don't want to do that. You can do:
LD_PRELOAD=x86_64-linux-gnu/desock.so:x86_64-linux-gnu/defork.so:x86_64-linux-gnu/dealarm.so \
~/code/security/codegate/2015/rodent/rodentPretty awesome stuff! Of course, you can pick and choose which preloads you want:
echo 'No fork or alarm for you, but I still want to netcat!'
LD_PRELOAD=x86_64-linux-gnu/defork.so:x86_64-linux-gnu/dealarm.so ~/code/security/codegate/2015/rodent/rodent
echo 'Ok, go ahead and fork, but no alarm. Time to brute force that canary.'
LD_PRELOAD=x86_64-linux-gnu/dealarm.so ~/code/security/codegate/2015/rodent/rodentHave fun!
The simple functionality in preeny is disabling of fork and alarm.
CTF services frequently use alarm to help mitigate hung connections from players, but this has the effect of being frustrating when you're debugging the service.
Fork is sometimes frustrating because some tools are unable to follow fork on some platforms and, when they do follow fork, the parent is oftentimes abandoned in the background, needing to be terminated manually afterwards.
dealarm.so replaces alarm() with a function that just does a return 0.
defork.so does the same thing to fork(), means that the program will think that the fork has succeeded and that it's the child.
It's often easiest to test your exploits without extra randomness, and then ease up on the cheating little by little.
Preeny ships with two modules to help: derand and desrand.
derand.so replaces rand() and random() and returns a configurable value. Just specify it in the RAND environment (or go with the default of 42):
# this will return 42 on each rand() call
LD_PRELOAD=x86_64-linux-gnu/derand.so tests/rand
# this will return 1337 on each rand() call
RAND=1337 LD_PRELOAD=x86_64-linux-gnu/derand.so tests/randFor slightly more complex things, desrand.so lets you override the srand function to your liking.
# this simply sets the seed to 42
LD_PRELOAD=x86_64-linux-gnu/desrand.so tests/rand
# this sets the seed to 1337
SEED=1337 LD_PRELOAD=x86_64-linux-gnu/desrand.so tests/rand
# this sets the seed to such that the first "rand() % 128" will be 10
WANT=10 MOD=128 LD_PRELOAD=x86_64-linux-gnu/desrand.so tests/rand
# finally, this makes the *third* "rand() % 128" be 10
SKIP=2 WANT=10 MOD=128 LD_PRELOAD=x86_64-linux-gnu/desrand.so tests/randdesrand does all this by brute-forcing the seed value, so keep in mind that startup speed will get considerably slower as MOD increases.
Certain tools (such as American Fuzzy Lop, for example) are unable to handle network binaries.
Preeny includes two "de-socketing" modules.
desock.so neuters socket(), bind(), listen(), and accept(), making it return sockets that are, through hackish ways, synchronized to stdin and stdout.
desock_dup.so is a simpler version for programs that dup accepted sockets over file descriptors 0, 1, and 2.
A discussion of the different ways to de-socket program, and why Preeny does it the way it does, can be found here.
You can also use preeny to turn a normal binary into a socket binary! Just set the PORT environment variable (default is 1337) and preload ensock.so!
patch.so patches binaries!
This is done before program start, by triggering the patcher from a constructor function in patch.so.
Patches are specified in a .ini format, and applied by including patch.so in LD_PRELOAD and providing a patch file specified by the PATCH environment variable.
For example:
# tests/hello
Hello world!
# cat hello.p
[hello]
address=0x4005c4
content='4141414141'
[world]
address=0x4005ca
content='6161616161'
# PATCH="hello.p" LD_PRELOAD=x86_64-linux-gnu/patch.so tests/hello
--- section hello in file hello.p specifies 5-byte patch at 0x4005c4
--- section world in file hello.p specifies 5-byte patch at 0x4005ca
AAAAA aaaaa!
Having different patch files and just enabling/disabling them via preload is oftentimes easier than modifying the underlying binary.
setstdin.so allows to replace STDIN with user defined data. It overrides read, fread, fgetc, getc and getchar calls, and
return user defined data when binary asks for some STDIN.
setstdin first tries to get user defined data form PREENY_STDIN environment variabe, if this variable is not defined, it tries to read data
from file, defined in PREENY_STDIN_FILENAME environment variable. If both are not defined, setstdin uses some default value.
$ PREENY_STDIN=New_message LD_PRELOAD=src/setstdin.so test/setstdin_read
N|ew|_me|ssag|e|
$ echo "Some other message" > tmp_file
$ PREENY_STDIN_FILENAME=tmp_file LD_PRELOAD=src/setstdin.so test/setstdin_getc
S|o|m|e| |o|t|h|e|r| |m|e|s|s|a|g|e|
$ LD_PRELOAD=src/setstdin.so test/setstdin_fread
D|ef|aul|t se|tstdi|n valu|e. Plea|se set P|REENY_STD|IN or PREE|NY_STDIN_FI|LENAME envir|onment variab|les to set you|r own value
pdeathsig.so makes a process -- and everything it goes on to spawn -- die when its parent dies.
It's for the situation where you kill the forking service you were poking at and it leaves a litter of orphaned children behind, still holding your listening port hostage.
$ LD_PRELOAD=x86_64-linux-gnu/pdeathsig.so ./some_forking_serverNow killing some_forking_server takes its workers, their workers, and anything it shelled out to down with it.
The mechanism is Linux's prctl(PR_SET_PDEATHSIG), which the kernel clears in every newly created task but preserves across execve().
So the module arms itself in three places:
- in its constructor, which covers every descendant that
exec()s --LD_PRELOADis inherited throughexec, so the constructor simply runs again over there. This is what coverssystem(),popen()andposix_spawn(), none of which reach an interposablefork()/clone()symbol, - in a
fork()/_Fork()/__fork()hook, which covers the children that neverexec(the classic forking server), and - in a
clone()/__clone()hook, for programs that callclone()themselves.
Every link only ever arms itself against its own immediate parent, and the kernel keeps the armed signal across reparenting, so the effect chains all the way down.
By default the signal is SIGKILL. PREENY_PDEATHSIG changes it, as a number or a name:
$ PREENY_PDEATHSIG=SIGTERM LD_PRELOAD=x86_64-linux-gnu/pdeathsig.so ./some_forking_server
$ PREENY_PDEATHSIG=15 LD_PRELOAD=x86_64-linux-gnu/pdeathsig.so ./some_forking_serverUnlike most preeny modules, this one validates its environment variable instead of just atoi()ing it, and it has to: atoi("SIGKILL") is 0, and prctl(PR_SET_PDEATHSIG, 0) succeeds -- it means "disable" -- so a typo would otherwise leave you with a module that loads, prints nothing, and does nothing.
An unparseable value gets an error and falls back to SIGKILL.
Things that will surprise you, in rough order of how likely you are to hit them:
- It fires when the parent thread exits, not the parent process. A child forked from a worker thread dies the moment that worker retires, even though the program is fine. That's the kernel's behavior, and no preload can fix it. It is this module's main source of false positives.
- Daemonizing does not work under it.
setsid, or any double-fork daemonize, exits the intermediate parent on purpose -- which is exactly the event this module kills on. Don't leavepdeathsig.soin a globalLD_PRELOAD; use it per-run. - Your shell is the parent. Preloading this means the process dies when the shell that started it dies. Usually that's the point.
- It's inert on setuid/setgid binaries. The loader ignores
LD_PRELOADfor them, and the kernel clears the parent-death signal on a privilegedexecveanyway. - A child that loses its parent mid-
execsurvives forever. On thesystem()/popen()/posix_spawn()path a child can only arm itself once its constructor runs, and a parent that died during theexechas by then already been replaced byinit-- so the child arms againstinitand nothing ever fires. Thefork()/clone()hooks don't have this hole, because they sample the parent's pid before forking, butprctl(PR_SET_PDEATHSIG)takes no pid, so there is nothing theexecpath can do about it. - The target can un-arm itself.
prctl(PR_SET_PDEATHSIG, 0)is not interposed, and -- less obviously -- any change to euid/egid/fsuid/fsgid or capabilities silently clears the parent-death signal in the kernel, so a daemon that drops privileges after startup loses it. The module exportsvoid preeny_pdeathsig_arm(pid_t); call it with0to re-arm if you're in a position to. - Delivery is permission-checked. A parent that has dropped privileges cannot signal a more-privileged child.
pthread_create()threads are never armed, which is correct -- a thread is not a process, and arming one would mean "kill this thread when the thread that created it exits".- Raw
syscall(SYS_clone)andclone3()are missed. There is nothing to interpose; glibc doesn't even export aclone3wrapper. clone(CLONE_SETTLS)tasks are not armed either. A child that starts on a thread pointer its caller built cannot safely run libc code, and arming toucheserrno, which lives relative to that pointer. Passing such clones straight through is the only correct thing to do.- Don't combine it with
defork.so. They both definefork(), and whichever comes first inLD_PRELOADwins, so the combination is meaningless -- though it is at least harmless:pdeathsigchecks that a fork really happened before doing anything, so it won't signal the target to death when stacked on afork()that returns 0 without forking.
pdeathsig only ever arms itself: prctl(PR_SET_PDEATHSIG) takes no pid, so there's no way to retroactively arm a child that started before the module loaded.
It also deliberately does not make the process a subreaper: that changes who adopts orphans rather than killing them, which is a surprising global side effect and not what you asked for.