Per-session command takeover engine for Android/Termux (bash)
Not a "fake permissions" gimmick and not a simple output rewriter. Within a session, every command — builtin or external, part of a compound chain or a pipeline, wrapped with any argument combination — is subject to the interception layer you define.
For authorized security research, education, and use on your own devices only. Misuse is against the law in most jurisdictions. You are responsible for what you run it on.
TerHijack is a layered interception system:
- Core engine (
terhijack/hijack) — intercepts commands inside the current bash session: rewrite output, replace the command, pass caller arguments through, take over entire compound chains, and even shadow builtins. - Binary-level layer (
--all/thj_patch) — ELF patch: replaces the real binaries (id cat head tail grep ls mount ps who last hostname whoami uptime free df uname) with a shim that answersOUT/BLOCK/FAKErecords — outliving the shell. Reversible with--restore. - Disguise suite (
root_disguise.sh) — builds a fully self-consistent fake-root environment on top: identity, SELinux, processes, networking, filesystem,/proc, plus self-hiding of the tool. - Collaboration layer (being integrated with aFakeSU / embedded proot) —
planned
ptracesyscall interception so even non-shell processes cannot bypass the disguise.
Sessions are clean by default; persistence is opt-in (--bash, --all).
| Mode | Example | Effect |
|---|---|---|
-o/--output |
hijack -c "id -Z" -o "root" |
rewrite a command's output |
-r/--recommand |
hijack -c "whoami" -r "echo root" |
transparently run another command instead |
-a/--arg |
hijack -c "su" -r "mysu" -a |
argument penetration: append the caller's args to the replacement (prefix match) |
| Compound chains | hijack -c "a && b && c" -r "echo x" |
take over the whole chain: && aborts, || short-circuits, ; siblings are neutered |
/ lists |
hijack -c "ls/pwd/id" -o "x" |
register several commands at once |
| Builtin takeover | hijack -c "exit" -o "MOCK" |
exit, logout, cd, echo and other builtins are shadowed too |
Long options (--command --output --recommand --arg --list --clear --init --remove --bash --all --restore) are aliases for the short ones.
eval "$(terhijack --init)" # bootstrap once per session
hijack -c "id -Z" -o "root" # rewrite output
hijack -c "echo $$" -r "echo root" # replace command
hijack -c "su" -r "mysu" -a # replace + pass args through
hijack -c "cd / && rm f && echo ok" -r "echo x" # whole chain taken over
hijack -c "ls/pwd/id" -o "x" # several commands at once
hijack --list # show active hooks
hijack -x "id -Z" # remove one hook
hijack --clear # drop every hook
# --- persistence ---
terhijack --bash > ~/hooked-bash # wrapper: hooks every new bash
alias bash="$HOME/hooked-bash"
hijack --all # patch real binaries (ELF shim)
hijack --restore # undo the binary patchTwo opt-in mechanisms make hooks survive the current shell; both are default-off and fully reversible.
terhijack --bash — persistent bash wrapper. Generates a small script
that, when used as bash, evals --init, reloads the saved hook state and
then execs the real bash. THJ_REAL_BASH / THJ_BIN / THJ_STATE_FILE
override resolution. Place it earlier in PATH or alias bash=... to hook
every new bash; management (hijack ...) works normally inside wrapped
sessions.
hijack --all — binary-level ELF patch. Takes the real binaries
(id cat head tail grep ls mount ps who last hostname whoami uptime free df uname), keeps each as <name>.thj_orig, and puts the thj_patch shim in
their place. Invocations dispatch on a .dat state file (records,
name<TAB>TYPE<TAB>payload):
OUT— print a fake payload (id→uid=0(root) ...) and exit 0BLOCK— refuse/proc&/sysaccess withPermission deniedFAKE— answertype <name>with the real (backup) path- (no record) — transparently run the real binary from the backup
Called with active session hooks (
hijack --all) it installs exactly those; called bare (terhijack --all) it installs defaults (id→root, cat/head/ tail/grep/ls→block).hijack --restorerestores every backup and clears the state. Env overrides for safe experimentation:THJ_DAT_FILE,THJ_PFX(where the binaries live — point it at a throwaway dir in tests).
State files. Set THJ_STATE_FILE (bash-hook TSV) and/or THJ_DAT_FILE
(shim .dat) and every hijack call re-persists them; __thj_load_file /
the --bash wrapper restore them. Nothing writes to disk unless one of these
is set.
- Argument-aware: with
-a, replacement functions receive the real caller arguments, enabling per-flag answers (e.g.idreturning precise values for-u,-g,-G,-n,-Z,-z,-aand any short-flag combination). - No collateral damage: exact matching only hits the identical argument
vector; unwrapped invocations pass through to the real binary (
command). - Cross-process: exported functions + a serialized state string
(
__THJ_SER) let child bash processes inherit every hook. - Truly suppressed, not faked: compound chains short-circuit via exit
codes, so the real commands never run (verified:
touchside effects are suppressed, not just the output masked). - Builtin-safe runtime: word counting uses pure parameter expansion — the
runtime does not depend on
echo/printfin matching paths, so shadowing builtins does not break the dispatcher. - Recursion-safe replacements: the triggering function is temporarily
unshadowed while the replacement runs, so a payload that itself calls the
hijacked name (e.g.
echoinside a-rchain) hits the real command. - Self-hijack is possible and articulate: hooking
hijack/terhijacklocks the management layer (recoverable with a re---init); hooking a widely used builtin has predictable cascade effects. Intentionally supported.
hijack() wrapper → binary (C) parses args + serialized session state
→ emits declarative state + runtime
__terhijack_dispatch: exact | prefix (-a) match →
out : print fake output
rec : eval replacement (+ caller args), real command re-enabled
for same-named calls inside the payload
else : passthrough via `command`
__terhijack_rebuild: unset old shims → reinstall shadow functions
__THJ_SER serialization + export -f → child bash inheritance
No DEBUG trap, no extdebug, no ptrace (yet) — everything is portable
bash function shadowing with declared session state.
One command turns the session into a self-consistent fake-root machine. Not per-command spot fixes — an environment where every probe returns a coherent story.
Identity
idwith full GNU flag support (-u -g -G -n -Z -z -a, combined shorts) →uid=0(root)+ faked SELinux context everywherewhoami,logname,groupsallroot; env rewritten (HOME=/root USER=root LOGNAME=root SHELL=/bin/bash PATH,HISTFILE,MAIL,PWD) and PS1 becomes a root-style#
Filesystem
catis path-aware:/etc/os-release,/etc/hostname,/etc/hosts,/etc/resolv.conf,/etc/environment,/etc/motdeach return bespoke faked content/procdeep fakery:/proc/<pid>/statusis regenerated per real PID withUid/Gid 0 0 0 0,CapPrm/CapEfffull,TracerPid: 0;/proc/<pid>/attr/currentreturns the faked context;environ,cmdline,commforgedls/statpresent root-owned file views;head/tail/grep/getentpass through with filtering;chown/chmodrespond like root
SELinux
getenforce→Enforcing;sestatus,chcon,setenforce,matchpathcon,seinfoall answer coherently
System / process / network
hostname,uname(kernel + arch),uptime,ps,mount,df,free,netstat,ss,ifconfig,ip
Accounts / management
sudo,passwd,crontab,w,who,last,lastb,history(a plausible root admin history)
Self-hiding
which,type,findfilter outterhijack,__root_*,__thj_*traces and report "normal"-looking paths for hooked coreutils
bash root_disguise.sh # enter a "looks like root" session
id # uid=0(root) ...
cat /proc/1/status # Uid: 0 0 0 0, CapEff full
type terhijack # not found (trace hidden)
hijack --clear # one-command restoretest.sh holds 52 assertions across every mode and its long-flag
aliases, built by repeatedly breaking the tool in real use:
| Area | What is verified |
|---|---|
| Output / replace / arg-pass | all three modes, short + long flags |
| Compound commands | && chain takeover, ; neutralization, real side-effects suppressed |
cat /proc/* |
not mis-split by the /-list heuristic |
| Path-vs-list disambiguation | cat /proc/x, cat ~/x, cat ./x stay single hooks; ls/pwd/id still splits |
| Builtin takeover | echo-level infrastructure commands |
| Argument boundaries | exact match does not leak to other invocations; -a passes multiple and space-containing args |
| Session boundaries | child bash inherits; --clear restores functions; fresh session clean |
| Leak surface | quoted/escaped payloads evaluated safely |
Persistence (--bash) |
hooks + management survive a fresh bash via the wrapper |
Binary layer (--all) |
OUT/BLOCK/FAKE/pass behaviors; --restore fully reverts |
Iterations driven by your requirements / bug reports:
- Compound
&&lines were mis-handled by the original DEBUG-trap approach → rewritten as segment shadowing + exit-code short-circuiting. cat /proc/versionwas once split by the/heuristic → added the" /"guard so real paths stay intact.-ainitially lost the caller's arguments → fixed by explicitly splicing"$@"into the replacement eval./proc/<pid>/statusdynamic UID/GID/Caps, SELinux context, and trace self-hiding were hardened across reported rounds.~/testandcat ~/xwere wrongly split into~/test/catby the/-list feature → every/segment must now be a clean command name, and~/.-paths are excluded;hijack -c "~/test"is rejected instead of silently mis-registered. (v1.2)
The shell layer is provably bypassable by anything that resolves commands
outside function shadowing (see Risks). The final layer, in progress with the
aFakeSU project (which embeds a proot/termux tree), is ptrace
syscall-interception: trace every child, translate paths, and fake return
values at the kernel interface. That closes the bypass surface for any
process, shell or not.
bash session: function shadowing + dispatch (done)
binary layer: thj_patch ELF shim (--all) (done, v1.2)
disguise: 40+ coherent __root_* payloads (done)
syscalls: proot-style ptrace interception (roadmap)
- Arbitrary code execution by design:
-rreplacements areeval'd in the session. A hook can run any command inside the victim shell, inheriting its environment, state, and hook control. This is the tool's nature, not a bug — assume any session running TerHijack is fully controllable by whoever defines the payloads. - Session-only illusion, easily bypassed — verified vectors:
- absolute paths —
$(/usr/bin/id -u)leaks the real UID - non-bash interpreters —
sh -c '<hooked yes> ; <bare command>'does not inherit functions; mksh/dash output leaks through - native syscalls —
python3 -c 'import os; print(os.getuid())'reads the real value from the kernel - SUID/exec'd binaries resolved by full path
- absolute paths —
- Builtin shadowing is sharp: hooking
echo/printfcatches every occurrence, including inside payloads (mitigated by the unshadow-during- eval rule) and the output branch still leans onprintf. - Hooking
hijack/terhijacklocks the management layer until a re---init; hooks persist through--clearonce the wrapper is shadowed. - Termux quirk:
/tmpis not writable — download-style payloads must land in$PREFIX/tmpor$HOME. - Payloads using the hijacked command cascade-trigger (e.g. a
-rchain onechocontainingecho); design triggers around non-builtin commands where possible. --alltouches real binaries ($PREFIX/bin/...→ shim, original kept as*.thj_orig). It is disabled-at-checkout by design and reversible with--restore, but if a backup goes missing the shim refuses to exec rather than loop. Test with aTHJ_PFXthrowaway dir first.--bashwrapper changes every new bash, including automation that spawnsbash; combined withTHJ_STATE_FILEit makes hooks durable across sessions, so a bad payload can follow you. Prefer session-scoped hooks for one-off work.
- Authorized penetration-testing / red-team exercises on systems you own or are permitted to test.
- Education: understanding how output/identity spoofing, OPSEC self-consistency, and shell-level interception work (and where they break).
- Prototyping the syscall-level interception in the aFakeSU collaboration.
Any other use — credential fishing, hiding tooling on shared systems, social engineering — is both against this project's stated purpose and, in most contexts, illegal. Keep it on your own devices.
make # cc -O2 -Wall -Wextra -std=c99 -> ./terhijack + ./thj_patch
make install # installs both to $PREFIX/bin/
bash test.sh # 52 functional assertionsRequires bash ≥ 4 (arrays, "${!arr[@]}"). zsh: function shadowing works;
-a and chain segmentation rely on bash extensions.
GPL-3.0. For research and education on your own devices — nothing more.