# shadow\_spider\_network

The attachment contains RC4 logic, but it is not actually used: When the input string overflows the buffer, the `__stack_chk_fail` is called, which is changed to some logic that triggers the signal handler, which actually checks the flag.

The rest is solved by AI.

Attack code:

```
# decompilation of the signal handler
# e.g.
# if ( *(_WORD *)(a3[15] + 17LL) == 125 )
# else if ( *(_BYTE *)(a3[15] - 19LL) == 109 )
flag = bytearray(82)
for line in open("decompiled.txt", "r"):
    if "a3[15]" in line:
        if "LL" in line.split()[5]:
            sign = line.split()[4]
            offset = int(line.split()[5].removesuffix("LL)"))
        elif "LL" in line.split()[6]:
            sign = line.split()[5]
            offset = int(line.split()[6].removesuffix("LL)"))
        else:
            sign = "+"
            offset = 0
        value = int(line.split()[-2])
        if sign == "+":
            offset = -offset
        flag[64 - offset] = value
print(flag)
```

Analysis by AI:

# TSGCTF 2025 - Shadow Spider Network Writeup

**Category:** Reverse Engineering  
**Difficulty:** Hard  
**Author:** TSG  
**Flag:** `TSGCTF{Inv3571ga710n_1n70_BOF_Or13n73d_Pr0gramm1ng_a5_a_73chn1qu3_f0r_0bfu5ca710n}`

## Challenge Overview

We are given a binary that appears to implement RC4 encryption/decryption for flag verification. The binary prompts for a flag and checks it against what seems to be RC4-encrypted data. However, there's a contradiction: the code checks `strlen(input) == 40` but then compares 48 bytes against RC4-decrypted data.

## Initial Analysis

### Basic Reconnaissance

```
$ file chall
chall: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=..., for GNU/Linux 3.2.0, stripped

$ checksec chall
Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled
```

### Main Function Analysis

The main function is simple:

```
void main() {
    printf("FLAG> ");
    if (verify_function()) {
        puts("Wrong");
    } else {
        puts("Correct!");
    }
    exit(0);
}
```

### Verification Function

The verification function `sub_40419F` appears to implement RC4:

1. Initializes S-box with 0-255
2. Uses a key from global variable `s` (37 bytes: "the\_flying\_cabbage\_eats\_purple\_clocks")
3. Performs RC4 key scheduling
4. Checks `strlen(input) == 40`
5. Generates RC4 keystream and compares 48 bytes against encrypted data at `byte_4050A0`

**Contradiction:** The function checks for 40-character input but compares 48 bytes!

## Deep Dive: Signal Handler Obfuscation

### Signal Handler Discovery

Looking at initialization functions, we find `sub_403F4F` which sets up signal handlers:

```
void setup_signals() {
    // Set up alternate signal stack
    struct sigaltstack s;
    memset(&s, 0, sizeof(s));
    s.ss_sp = malloc(sysconf(_SC_PAGESIZE));
    s.ss_size = sysconf(_SC_PAGESIZE);
    s.ss_flags = 0;
    sigaltstack(&s, 0);

    // Set up signal handler
    struct sigaction act;
    memset(&act, 0, sizeof(act));
    act.sa_handler = (__sighandler_t)sub_401363;  // Signal handler
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_ONSTACK;  // Use alternate stack

    // Register for SIGSEGV (11) and SIGILL (4)
    sigaction(11, &act, 0);  // SIGSEGV
    sigaction(4, &act, 0);   // SIGILL
}
```

### Signal Handler Analysis

The signal handler `sub_401363` is complex (1455 lines of decompiled code). It implements a state machine that:

- Checks the current RIP (`a3[21]`) to determine state
- For each state, checks a specific byte at an offset from `a3[15]`
- If byte matches expected value, sets next RIP to continue
- If mismatch, sets RIP to crash function (`sub_401080`)

The handler has 82 unique character checks with offsets from -64 to +17.

## The Real Verification Mechanism

### Understanding the Signal Handler Checks

The signal handler checks take the form:

```
if (*(_BYTE *)(a3[15] + offset) == expected_char)
```

Where:

- `a3[15]` is the faulting address (RIP in signal context)
- `offset` ranges from -64 to +17
- `expected_char` is an ASCII value

### The Buffer Overflow Trick

The verification function uses:

```
_BYTE v1[56];  // 56-byte buffer
__isoc99_scanf("%s", v1);  // Unsafe - no length limit!
```

The program:

1. Reads input with `scanf("%s", v1)` which can overflow the 56-byte buffer
2. Intentionally causes faults at `input + 64`
3. Signal handler checks 82 bytes around the faulting address

### Mapping Offsets to Input

If the program faults at `input + 64`, then:

- `a3[15]` (RIP) = `input + 64`
- `input[0]` is at offset -64
- `input[1]` is at offset -63
- ...
- `input[81]` is at offset +17

Thus, the input needs to be 82 characters to satisfy all checks.

## Extracting the Flag

### Signal Handler Character Checks

By analyzing the signal handler, we extract all 82 (offset, char) pairs:

| Offset | Char | Decimal | Description |
| --- | --- | --- | --- |
| \-64 | T | 84 | Start of flag |
| \-63 | S | 83 |  |
| \-62 | G | 71 |  |
| \-61 | C | 67 |  |
| \-60 | T | 84 |  |
| \-59 | F | 70 |  |
| \-58 | { | 123 | Flag opening |
| \-57 | I | 73 |  |
| ... | ... | ... | ... |
| \+17 | } | 125 | Flag closing |

### Reconstructing the Flag

We wrote a Python script to reconstruct the flag from the checks:

```
# Map offsets to indices: index = offset + 64
checks = [
    (-64, 84, 'T'), (-63, 83, 'S'), (-62, 71, 'G'),
    (-61, 67, 'C'), (-60, 84, 'T'), (-59, 70, 'F'),
    (-58, 123, '{'), (-57, 73, 'I'), (-56, 110, 'n'),
    # ... all 82 checks ...
    (+17, 125, '}')
]

# Reconstruct
flag_chars = ['?'] * 82
for offset, decimal, char in checks:
    index = offset + 64
    flag_chars[index] = char

flag = ''.join(flag_chars)
```

### The Complete Flag

```
TSGCTF{Inv3571ga710n_1n70_BOF_Or13n73d_Pr0gramm1ng_a5_a_73chn1qu3_f0r_0bfu5ca710n}
```

**Translation (with leetspeak decoded):**  
"Investigation into BOF Oriented Programming as a technique for obfuscation"

## Solving the Contradiction

### How the 40 vs 48 Byte Issue is Resolved

1. **Normal execution path:** `strlen(input) == 40` check fails for 82-byte input
2. **Signal handler intervention:** The program intentionally causes signals
3. **Signal handler modifies execution:** Changes RIP or other registers to bypass the check
4. **State machine completion:** If all 82 checks pass, signal handler sets success state

### RC4's Actual Role

The RC4 verification at `loc_404486` is just **one state** in the larger signal handler state machine. It's not the primary verification mechanism but rather part of the obfuscation.

## Solution Script

```
#!/usr/bin/env python3
import subprocess

flag = "TSGCTF{Inv3571ga710n_1n70_BOF_Or13n73d_Pr0gramm1ng_a5_a_73chn1qu3_f0r_0bfu5ca710n}"

# Run the binary
proc = subprocess.run(
    ["./chall"],
    input=flag + "\n",
    text=True,
    capture_output=True
)

print(proc.stdout)
# Output: FLAG> Correct!
```

## Key Takeaways

1. **Signal-based Control Flow:** The challenge uses signal handlers (SIGSEGV/SIGILL) for control flow obfuscation
2. **Buffer Overflow as Feature:** Intentional buffer overflow is part of the verification mechanism
3. **State Machine Verification:** Flag verification happens through a state machine in the signal handler
4. **Red Herrings:** RC4 and the 40-byte check are distractions from the real verification
5. **Context Awareness:** Understanding signal context (`ucontext_t`) is crucial for solving

## Tools Used

- Python for analysis and automation
- GDB for debugging (though patching was needed to bypass anti-debugging)
- Signal handler analysis tools

## Flag

`TSGCTF{Inv3571ga710n_1n70_BOF_Or13n73d_Pr0gramm1ng_a5_a_73chn1qu3_f0r_0bfu5ca710n}`

## References

- Linux `ucontext_t` structure and signal handling
- RC4 algorithm implementation
- Buffer overflow exploitation techniques
- Signal-based control flow obfuscation patterns
