PQC Verified

The Self-Descriptive Code: Holding the Mean in the Age of LLMs

by gg582 · 2026-09-20 14:32:20 · 113 views · 10 min read

Table of contents

LLMs still code, but they don't separate boundaries

Suppose we are developing an application for x86-64 Linux PC that hijacks stdin, stdout, and stderr from other processes and attaches them to the current terminal.

It is not that complex and is easy to write. Therefore, many programmers generate such programs with LLMs, and they can actually be useful.

However, when we try to extend those small programs, it becomes increasingly difficult when we tell the AI, "Please extend these features." I noticed this while developing small apps and tiny frameworks.

Here is AI-generated code for an "I/O hijacker":

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <sys/syscall.h>

// single-syscall injection function for x86_64
long inject_syscall(pid_t pid, struct user_regs_struct *orig_regs, long syscall_nr,
                   long arg1, long arg2, long arg3) {
    struct user_regs_struct regs = *orig_regs;
    
    // x86_64 ABI: rax=sys_nr, rdi=arg1, rsi=arg2, rdx=arg3
    regs.rax = syscall_nr;
    regs.rdi = arg1;
    regs.rsi = arg2;
    regs.rdx = arg3;

    ptrace(PTRACE_SETREGS, pid, NULL, &regs);
    ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL);
    waitpid(pid, NULL, 0);

    // re-read registers to return the result (rax)
    ptrace(PTRACE_GETREGS, pid, NULL, &regs);
    return regs.rax;
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <target_pid>\n", argv[0]);
        return 1;
    }

    pid_t pid = atoi(argv[1]);
    char *tty_path = ttyname(STDIN_FILENO);
    if (!tty_path) {
        perror("ttyname");
        return 1;
    }

    printf("[+] Target PID: %d\n", pid);
    printf("[+] Current TTY: %s\n", tty_path);

    // 1. take control of the target process
    if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
        perror("ptrace attach");
        return 1;
    }
    waitpid(pid, NULL, 0);

    // 2. back up current registers and memory
    struct user_regs_struct orig_regs, regs;
    ptrace(PTRACE_GETREGS, pid, NULL, &orig_regs);
    regs = orig_regs;

    // back up the original instruction at RIP and inject a 'syscall' opcode (0x050f)
    long orig_code = ptrace(PTRACE_PEEKTEXT, pid, (void *)regs.rip, NULL);
    long syscall_code = (orig_code & ~0xFFFF) | 0x050f;
    ptrace(PTRACE_POKETEXT, pid, (void *)regs.rip, (void *)syscall_code);

    // write the TTY path string into the stack area 128 bytes below RSP
    unsigned long target_buf = regs.rsp - 128;
    size_t len = strlen(tty_path) + 1;
    for (size_t i = 0; i < len; i += sizeof(long)) {
        long chunk = 0;
        memcpy(&chunk, tty_path + i, (len - i < sizeof(long)) ? len - i : sizeof(long));
        ptrace(PTRACE_POKETEXT, pid, (void *)(target_buf + i), (void *)chunk);
    }

    // 3. inject sys_open(tty_path, O_RDWR)
    long new_fd = inject_syscall(pid, &regs, SYS_open, target_buf, O_RDWR, 0);
    printf("[+] Injected open() -> new fd: %ld\n", new_fd);

    if (new_fd >= 0) {
        // 4. inject sys_dup2(new_fd, 0), sys_dup2(new_fd, 1), sys_dup2(new_fd, 2)
        inject_syscall(pid, &regs, SYS_dup2, new_fd, 0, 0); // stdin
        inject_syscall(pid, &regs, SYS_dup2, new_fd, 1, 0); // stdout
        inject_syscall(pid, &regs, SYS_dup2, new_fd, 2, 0); // stderr
        inject_syscall(pid, &regs, SYS_close, new_fd, 0, 0);
        printf("[+] Successfully redirected stdio!\n");
    }

    // 5. restore original memory and registers, then detach
    ptrace(PTRACE_POKETEXT, pid, (void *)regs.rip, (void *)orig_code);
    ptrace(PTRACE_SETREGS, pid, NULL, &orig_regs);
    ptrace(PTRACE_DETACH, pid, NULL, NULL);

    return 0;
}

Do you get it? Nah, I don't get it. All the features are stuffed inside the main function.

  • The main function is too fat.

  • It uses SYS_open instead of SYS_openat.

  • The comments are awkward:

  • They do not describe what is actually happening on the machine.

  • The numbered comments are unnatural (e.g., 5. describes ----, then --.).

  • Each system call looks like an ancient spell.

Because the code is almost unreadable, we cannot organize it for the next step.

Refactoring the ancient spell

  • Large steps should be refactored into distinct functions.

  • Comments should explain what the system call actually does:

    • Comments should follow the logical flow.
    • Comments should explicitly show each flag and call.
  • Use SYS_openat to make the pathname resolution explicit.

  • Extend features when appropriate:

  • LLMs tend to be conservative when writing small tools.

  • When rewriting a program, some parts are quite easy to extend.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <sys/syscall.h>

// 8-byte packing macro
#define PACK64(a,b,c,d,e,f,g,h) \
    (((uint64_t)(a) << 56) | ((uint64_t)(b) << 48) | \
     ((uint64_t)(c) << 40) | ((uint64_t)(d) << 32) | \
     ((uint64_t)(e) << 24) | ((uint64_t)(f) << 16) | \
     ((uint64_t)(g) <<  8) | ((uint64_t)(h)))

#define CMP_STDIN  PACK64('s','t','d','i','n', 0 , 0 , 0 )
#define CMP_STDOUT PACK64('s','t','d','o','u','t', 0 , 0 )
#define CMP_STDERR PACK64('s','t','d','e','r','r', 0 , 0 )

#define TTY_PATH_MAX 256

// inject syscall
long inject(pid_t pid, struct user_regs_struct *orig, long nr, long *args) {
    struct user_regs_struct regs = *orig;
    // Syscall NR
    regs.rax = nr;
    //  | rdi(long) | rsi(long) | rdx(long) | r10(long)
    regs.rdi = args[0];
    regs.rsi = args[1];
    regs.rdx = args[2];
    regs.r10 = args[3];

    // replace the tracee's register state with the prepared syscall state
    if(ptrace(PTRACE_SETREGS, pid, NULL, &regs) == -1) {
        fputs("ptrace(PTRACE_SETREGS) failed", stderr);
        return -1;
    }
    // execute the injected syscall instruction at the current RIP
    if(ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL) == -1) {
        fputs("ptrace(PTRACE_SINGLESTEP) failed", stderr);
        return -1;
    }
    if(waitpid(pid, NULL, 0) == -1) {
        fputs("waitpid failed", stderr);
        return -1;
    }
    if(ptrace(PTRACE_GETREGS, pid, NULL, &regs) == -1) {
        fputs("ptrace(PTRACE_GETREGS) failed", stderr);
        return -1;
    }

    // return result (RAX)
    return regs.rax;
}

/// parse a flag and return.
/// return value: 001(2): stdin
///               010(2): stdout
///               100(2): stderr
///
///               example: 011(2): stdin|stdout
///                        111(2): stdin|stdout|stderr
///                        110(2): stdout|stderr
///                        101(2): stderr|stdin
/// pack up to 8 bytes of a string into an int64; big-endian order to match PACK64
int64_t pack_flag(const char *s) {
    int64_t v = 0;
    for(int i = 0; i < 8 && s[i] != '\0'; i++) {
        v |= ((uint64_t)(unsigned char)s[i]) << (56 - i * 8);
    }
    return v;
}

int8_t parse_flag(const char *flags) {
    int8_t flag_stdin =  1<<0;
    int8_t flag_stdout = 1<<1;
    int8_t flag_stderr = 1<<2;

    /// flag to return
    int8_t final_flag = (int8_t)0;


    char *savePtr;
    char *token;
    const char *delim = "|";

    token = strtok_r((char *)flags, delim, &savePtr);

    while(token) {
        switch(pack_flag(token)) {
            case CMP_STDIN:
                final_flag |= flag_stdin;
                break;
            case CMP_STDOUT:
                final_flag |= flag_stdout;
                break;
            case CMP_STDERR:
                final_flag |= flag_stderr;
                break;
        }
        token = strtok_r(NULL, delim, &savePtr);
    }
    return final_flag;
}

void help(char *argv[]) {
    // --help: 6 bytes
    // -h: 2 bytes
    if(!strcmp("--help", (const char *)argv[1])
     ||!strcmp("-h",(const char *)argv[1])) {
        const char help[1024] = "jackpr is a minimalist utility for taking a running program and attaching it to the current terminal.\r\n"
                                   "jackpr <target_pid>: Hijack target_pid's stdin, stdout, stderr into current pty/tty.\r\n"
                                   "jackpr <target_pid> stdin: Hijack target_pid's stdin into current pty/tty.\r\n"
                                   "jackpr <target_pid> stdout: Hijack target_pid's stdout into current pty/tty.\r\n"
                                   "jackpr <target_pid> stderr: Hijack target_pid's stderr into current pty/tty.\r\n"
                                   "jackpr <target_pid> stdin|stdout: Hijack target_pid's stdin and stdout into current pty/tty.\r\n"
                                   "jackpr <target_pid> stdin|stderr: Hijack target_pid's stdin and stderr into current pty/tty.\r\n"
                                   "jackpr <target_pid> stdout|stderr: Hijack target_pid's stdout and stderr into current pty/tty.\r\n"
                                   "visit https://github.com/gg582/jackpr to see more.\r\n";
        fputs(help, stderr);
        exit(0);
    }
}

/** check if given fd is connected to the current terminal
 *
 * | ... | RIP | .... (256 bytes) |
 * there's no need to use TTY_PATH_MAX as 4096.
**/
_Bool check_io_connections(int fd, char *tty_path) {
    int result = ttyname_r(fd, tty_path, TTY_PATH_MAX);
    if(result) {
        fprintf(stderr, "%s is disconnected. (code: %d)", !fd ? "stdin" : (fd == 1 ? "stdout": "stderr"), result);
        return 0;
    }
    return 1;
}

void attach_to_pid(int pid) {
    if(ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
        fputs("error: failed to attach to a target pid.", stderr);
        exit(-1);
    }
    waitpid(pid, NULL, 0);
}

void hijack_terminal(int pid, int new_fd, struct user_regs_struct *regs, long *args, int8_t flag) {
    for(int i = 0;
            i < STDIN_FILENO + STDOUT_FILENO + STDERR_FILENO;
            i++) {
        args[0] = new_fd;
        args[1] = i;
        args[2] = args[3] = 0;
        if((flag >> i) & 1) {
            int result = inject(pid, regs, SYS_dup2, args);
            if(result == -1) exit(-1);
        }
    }
}

/// run jackpr
int run_jackpr(int argc, char *argv[])
{
    if(argc < 2) {
        fprintf(stderr, "usage: %s <pid>\n", argv[0]);
        return 1;
    }
    if(argc >= 2) {
        // help cmd
        help(argv);

        char tty_path[TTY_PATH_MAX];
        memset(tty_path, 0, TTY_PATH_MAX);

        // 111(2) == 7(10)
        int8_t flag = 7;

        if(argc == 3) {
            if(argv[2] != NULL) {
                flag = parse_flag((const char *)argv[2]);
            }
        }

        char *endptr;
        errno = 0;
        // input: 0123, result: 123
        // 0123 should not be 123(8)
        long pid = strtol(argv[1], &endptr, 10);
        if(endptr == argv[1]) {
            fputs("invalid pid", stderr);
            return -1;
        }
        if((errno == ERANGE)
        || (pid <= 0)
        || (pid > INT_MAX)) {
            fprintf(stderr, "invalid pid: %s\n", argv[1]);
            return -1;
        }

        if(*endptr) {
            fprintf(stderr, "invalid pid: %s\n", argv[1]);
            return -1;
        }

        else {
            // check if stdin, stdout, stderr are connected to the current terminal
            _Bool is_connected[STDIN_FILENO + STDOUT_FILENO + STDERR_FILENO] = { 0, 0, 0 };
            for(int i = 0;
                    i < STDIN_FILENO + STDOUT_FILENO + STDERR_FILENO;
                    i++) {

                is_connected[i] = check_io_connections(i, tty_path);
                if(!is_connected[i] && ((flag >> i) & 1)) {
                    return -1;
                }
            }

            if(!((is_connected[0] | is_connected[1]) | is_connected[2])) {
                fputs("stdin, stdout, stderr are all disconnected. cannot hijack.", stderr);
                return -1;
            }

            // take controls over target pid
            attach_to_pid(pid);

            // backup original registers
            struct user_regs_struct orig_regs;
            ptrace(PTRACE_GETREGS, pid, NULL, &orig_regs);

            // backup original memory
            long orig_code = ptrace(PTRACE_PEEKTEXT, pid, (void *)orig_regs.rip, NULL);

            // wipe out last 2 bytes.
            // ~0xFFFF == 0xFFFFFFFFFFFF0000
            // 0xFFFFFFFFFFFF0000 & orig_code
            //
            // Op      | Result
            // 0 AND 0 | 0
            // 0 AND 1 | 0
            // 1 AND 0 | 0
            // 1 AND 1 | 1
            // 0 AND N is always zero. N = {0, 1}
            // ~0xFFFF's last 2 bytes are 0x00 and 0x00.
            // replace the instruction at RIP with the x86-64 syscall opcode.
            //   C integer: 0x050f
            //   memory:    0x0f 0x05
            // AND clears the original low 16 bits
            // OR places the syscall opcode into those bits
            // |........ ........ ........ ........| 0f 05 |
            // 0x050f is the little-endian integer representation
            // of the x86-64 syscall instruction bytes.
            long call = (orig_code & ~0xFFFF) | 0x050f;
            ptrace(PTRACE_POKETEXT, pid, (void *)orig_regs.rip, (void *)call);

            // place tty_path outside the SysV AMD64 red zone:
            //
            //                  RSP
            //                   |
            //                   v
            //   +-------------------------------+
            //   |           red zone            |
            //   |             128 B             |
            //   +-------------------------------+
            //   |                               |
            //   |         tty_path buffer       |
            //   |                               |
            //   +-------------------------------+
            //                   ^
            //                   |
            //               RSP - 256
            //
            // Keep the buffer below the 128-byte red zone;
            // we therefore use RSP - 256 rather than RSP - 128.
            unsigned long target_buf = orig_regs.rsp - 256;

            size_t len = strlen(tty_path) + 1;
            for(size_t i = 0; i < len; i += sizeof(long)) {
                long chunk = 0;
                memcpy(&chunk, tty_path + i, (len - i < sizeof(long)) ? len - i : sizeof(long));
                ptrace(PTRACE_POKETEXT, pid, (void *)(target_buf + i), (void *)chunk);
            }

            // open tty_path for reading and writing.
            long args[4] = { AT_FDCWD, target_buf, O_RDWR, 0 };
            long new_fd = inject(pid, &orig_regs, SYS_openat, (long *)args);

            if(new_fd >= 0) {
                // duplicate the tty fd onto the selected standard streams.
                hijack_terminal(pid, new_fd, &orig_regs, args, flag);
                
                // set arguments to zero before closing new_fd
                args[0] = new_fd;
                args[1] = args[2] = args[3] = 0;
                // close new_fd at the target process
                inject(pid, &orig_regs, SYS_close, args);
            }

            // restore original memory
            ptrace(PTRACE_POKETEXT, pid, (void *)orig_regs.rip, (void *)orig_code);
            // restore original registers
            ptrace(PTRACE_SETREGS, pid, NULL, &orig_regs);
            // detach from the tracee and resume it.
            ptrace(PTRACE_DETACH, pid, NULL, NULL);
            
        }
    }
    return 0;
}

int main(int argc, char *argv[]) {
    return run_jackpr(argc, argv);
}

It is better to describe the program itself. Do not list obvious facts like AIs do.

What I am still trying to explain is...

It is NOT a conclusion that we should avoid AI.

Even if some code is generated by LLMs, if it self-describes well enough, we should not reject those lines.

If we pretend to be "saviors of humanity" while assuming all AI-generated code is slop, we are just indulging in another form of prejudice.

However, we still need to be wary of code that cannot explain itself.

P.S.

Some maintainers, like Andrew Kelley, reject AI-generated code outright.

Some open-source projects, like Debian, allow AI usage, but require developers to take full responsibility for their contributions.

Some people are vibe coders, and some even lose their grasp of the fundamentals while relying on AI assistants.

Each decision is understandable, and different cultures form different perspectives.

However, we should understand the principle underlying these policies. In my opinion, nitpicking like an old clergyman over whether code is AI-generated misses the point entirely.

Therefore, I close this post with a famous Asian quote from 'The Book of Documents(書經)':

人心惟危 Human hearts are precarious,

道心惟微 The moral mind is subtle.

惟精惟一 Refine yourself and remain consistent,

允執厥中 Sincerity lies in holding fast to the mean.

Related posts

Back
Report

Comments

No comments yet.