8
\$\begingroup\$

As a learning exercise I wrote a short C program that changes the instructions of a function at runtime in order to execute a shell. It's obviously dependent on x86_64 architecture and Linux (for the syscall number). Indeed, it does start a shell when I run it. I was told to post it here by someone on StackOverflow so what I'm wondering is if there is anything that I'm overlooking in the overall concept or if anything can be improved?

For the sake of brevity, below is only the final product. I wrote up a much longer explanation of the whole process on my blog, but I'm only concerned about the code below, not my long explanation of it.

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>

void foo(void);
int change_page_permissions_of_address(void *addr);

int main(void) {
    void *foo_addr = (void*)foo;

    // Change the permissions of the page that contains foo() to read, write, and execute
    // This assumes that foo() is fully contained by a single page
    if(change_page_permissions_of_address(foo_addr) == -1) {
        fprintf(stderr, "Error while changing page permissions of foo(): %s\n", strerror(errno));
        return 1;
    }

    puts("Calling foo");
    foo();

    char shellcode[] =
        "\x48\x31\xd2"                              // xor    %rdx, %rdx
        "\x48\x31\xc0"                              // xor    %rax, %rax
        "\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00"  // mov    $0x68732f6e69622f2f, %rbx
        "\x53"                                      // push   %rbx
        "\x48\x89\xe7"                              // mov    %rsp, %rdi
        "\x50"                                      // push   %rax
        "\x57"                                      // push   %rdi 
        "\x48\x89\xe6"                              // mov    %rsp, %rsi 
        "\xb0\x3b"                                  // mov    $0x3b, %al
        "\x0f\x05";                                 // syscall

    // Careful with the length of the shellcode here depending on what is after foo
    memcpy(foo_addr, shellcode, sizeof(shellcode));

    puts("Calling foo");
    foo();

    return 0;
}

void foo(void) {
    int i=0;
    i++;
    printf("i: %d\n", i);
}

int change_page_permissions_of_address(void *addr) {
    // Move the pointer to the page boundary
    int page_size = getpagesize();
    addr -= (unsigned long)addr % page_size;

    if(mprotect(addr, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1) {
        return -1;
    }

    return 0;
}
\$\endgroup\$
6
\$\begingroup\$

It looks pretty good to me. I can see that a lot of research has gone into this. There are a few things I would fix though.

  • You print "Calling foo" right before you call it, which I don't really see a need for since you are printing something that looks like a counter within foo(). I would remove them.

  • For your shellcode[] you comment what all the hex does in Assembly, which is good. But I would take it a step farther, and comment on what the Assembly actually does, since it can be hard to follow sometimes.

  • Some of your names are a bit long, such as change_page_permissions_of_address(). That can be a task to type without an IDE, and annoying if you misspell it. Perhaps you should make the name shorter.

\$\endgroup\$
  • 1
    \$\begingroup\$ Thanks for looking over it! Printing "Calling foo" was just more to illustrate that foo() was changed, but, yes, it can be removed. To address points 2 and 3, I have a very detailed explanation of the shellcode in a blog post since it would be very lengthy to make a comment in the program. I'm a fan of long function names that describe what the function does without a need for a comment when calling it. Plus, I use an editor with auto-complete so it's no big deal to type. \$\endgroup\$ – shanet Jan 28 '14 at 0:52
  • 1
    \$\begingroup\$ @shanet Overall your code was very good, and what I had was just nitpicks. Hopefully you can ask some more questions here in the future! \$\endgroup\$ – syb0rg Jan 28 '14 at 0:54
7
\$\begingroup\$

There's one small bug in your code here:

memcpy(foo_addr, shellcode, sizeof(shellcode));

This copies the contents of shellcode, but being a string literal, shellcode includes a null terminator and sizeof(shellcode) is equal to 30, not 29. You should replace this line with:

memcpy(foo_addr, shellcode, sizeof(shellcode) - 1);
\$\endgroup\$
  • 1
    \$\begingroup\$ Good catch, thanks. Darn off-by-one errors! \$\endgroup\$ – shanet Jan 28 '14 at 1:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.