3

Im trying to write a simple program that hooks into another binary (setuid binary, child process run it) using ptrace(), opens a 'flag' file and prints its content. The hook works, and i can control the binary using ptrace commands. I've executed syscall open(), and got a legitimate file descriptor(4).

My problem is that syscall read() fails, and returns a negative value of bytes, -21. errno is set to 0 (success). The machine is remote (32-bits), and i cannot install libexplain library there. I've searched the web ALOT, yet found no answers regarding the meaning of negative return values of read() syscall (according to the manual page, its only error value is -1..).

enter image description here

parent's code:

int run_syscall(pid_t pid, int syscall_number, int first_paramter, int second_parameter, int third_paramter) {
struct user_regs_struct regs = { 0 };
ptrace(PTRACE_GETREGS, pid, 0, &regs);
regs.eip = INT80;
regs.eax = syscall_number;
regs.ebx = first_paramter;
regs.ecx = second_parameter;
regs.edx = third_paramter;
ptrace(PTRACE_SETREGS, pid, 0, &regs);
ptrace(PTRACE_SINGLESTEP, pid, 0, 0);}


// eax = 5 (open), ebx = 'flag' path, ecx = 0, edx = 0
run_syscall(pid, 5, 0x8048200, 0 , 0); 
wait(NULL);
ptrace(PTRACE_GETREGS, pid, 0, &regs);
int flag_fd = regs.eax;                 // success, returns fd = 4
printf("Open syscall worked, flag fd: %d\n", flag_fd);
printf("[*]Syscall was made..\nRegisters values:\nEAX:%08x\nORIG_EAX:%08x\nESP:%08x\nEBP:%08x\nEIP:%08x, should be 0xf7742b59\n\n\n\n\n\n", regs.eax, regs.orig_eax, regs.esp, regs.ebp, regs.eip);
long flag_addr = ptrace(PTRACE_PEEKTEXT, pid, regs.esp + 0x1000 - 8, 0);
long flag = ptrace(PTRACE_PEEKTEXT, pid, flag_addr, 0);
printf("Before read() text: %p\n", flag);

// eax = 3 (read), ebx = opened filedescriptor, ecx = address i want to store the flag content, edx = 1 byte to read
run_syscall(pid, 3, flag_fd, flag_addr, 1); // Read
wait(NULL);
ptrace(PTRACE_GETREGS, pid, 0, &regs);
printf("read %d bytes\n",  regs.eax); // regs.eax = -21 
printf("errno: %d\n", errno); // errno = 0
printf("Description: %s\n", strerror(errno));
flag = ptrace(PTRACE_PEEKTEXT, pid, flag_addr, 0);
printf("After read() text: %p\n", flag); // remains the same

i made sure that the destination address (flag_addr) is located in writeable memory area (the stack).

I've also tried different sizes to read (edx), in order to check if something was wrong with the alignment. Unfortunately the bug remains, and read() always returns with -21 (even if edx set to 0).

I will be very glad if someone knows what status the return value of -21 means, and how can i fix my code..

thank you.

EDIT: solved. The problem was that the file was named 'flag.txt', but it was a directory! what a troll

meowi
  • 66
  • 7
  • I think this would benefit from a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of at least part of the problem. Where does `run_syscall` come from? – Gnoom Feb 13 '21 at 13:25
  • @Gnoom oh im sorry it wasn't understandable, wanted to make code as minimal as possible. Added – meowi Feb 13 '21 at 13:31
  • *I've executed syscall open(), and got a legitimate file descriptor(4)* How do you know it's a "legitimate file descriptor" other than the value? What exactly did you try to open? And once you get past those, if you're running a 64-bit binary you have some serious 64-bit issues (hint: you can't cram values like pointers and `ssize_t` values into a mere `int`...) – Andrew Henle Feb 13 '21 at 13:39
  • Its a 32-bit binary (compiled using -m32). Tried to open setuid text file (the child runs a setuid binary), and according to 'man open', positive fd means success (couldn't check any further, as read fails..) – meowi Feb 13 '21 at 13:42
  • Though the edit is not inappropriate, the correct action is to [accept/vote the answer](https://meta.stackexchange.com/a/5235/684852) that solved your problem. – anastaciu Feb 13 '21 at 14:55

1 Answers1

5

While read(2) can't return -21, the read system call can. In other words, you are overlooking a layer between the kernel and your program; libc. Have a look at these snippets from musl libc:

#define syscall_cp(...) __syscall_ret(__syscall_cp(__VA_ARGS__))

long __syscall_ret(unsigned long r)
{
    if (r > -4096UL) {
        errno = -r;
        return -1;
    }
    return r;
}

ssize_t read(int fd, void *buf, size_t count)
{
    return syscall_cp(SYS_read, fd, buf, count);
}

Most likely this means that the syscall returns the error code 21, likely meaning EISDIR 21 Is a directory

wkz
  • 2,203
  • 1
  • 15
  • 21
  • Very interesting that the libc wrapper doesn't completely follows the syscall functionality, learned something new today. However this file is 100% not a directory... ill keep research the syscall read – meowi Feb 13 '21 at 14:18
  • @meowi Whoever characterized POSIX `read()` as a "system call" was wrong - it's implemented as a ***function*** just like "functions" such as `fopen()`. The "`fopen()` is a function and `read()` is a system call" distinction is somewhere between misleading and downright wrong. *doesn't completely follows the syscall functionality*? That's the wrong way to approach programming - there's no such thing as ***the*** "syscall functionality". Implementations are free to do whatever they'd like. And if you go digging, you find things like the `open()` ***function*** using the `openat()` syscall. – Andrew Henle Feb 13 '21 at 14:25