0

In the following code, I want to change the arguments of main function without modifying main function.

Recently, I read the code of AFL fuzzing tool, for the forkserver, I do not understand how to pass the parameters to the target program, so I write this simple program.

I have tried to use pipe to redirect stdin and write parameters to stdin, but it does not work.

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

__attribute__((constructor))
void __start() {
    static unsigned char tmp[4];
    int count = 0;
    pid_t pid;
    int status;
    while(count < 3){
        pid = fork();
        if(pid < 0) _exit(1);
        if(!pid){
            //how to pass "hello" to main without modfiying main function.
            return;
        }

        count++;
        if(count == 3) _exit(1);
    }
    //_exit(0);

}

int main(int argc, char** argv){
  for(int i = 0; i < argc; i++){
    printf("%d %s\n", i, argv[i]);
  }
  return 0;
}

If "hello" in argv, argc should be 2 and the output should have "1 hello".

jackzhou
  • 29
  • 4
  • 1
    Simple solution:call `execvp` (or some other `exec*`) after you fork. You don't need to redirect stdin to stdin after the fork, but you might want to redirect it to *something else* because having multiple forked children with the same stdin doesn't work and multiple forked children sharing the same `stdout` and/or `stderr` tends to result in unreadable interleaved output. – rici Jun 20 '19 at 06:07
  • 1
    The system call exexvp (or other exec*) is used to execute a program. In my program, the __start function is running before main, so when we want to run the actual program--main() many times, we do not need to load and initialize it many times like the forkserver of afl-fuzz. – jackzhou Jun 20 '19 at 06:58
  • These might help: https://stackoverflow.com/a/3848603/12711 and https://stackoverflow.com/q/12076848/12711 – Michael Burr Jun 20 '19 at 07:15
  • Thanks! @MichaelBurr These two links are useful. Although we can parse the argv without argc, some real program also need argc to parse argv. If I want to instrument start function into a binary file, but it need argc like the above main function, so how the argc and argv of main can be changed in start dynamically? – jackzhou Jun 20 '19 at 08:22
  • From the fact that you are prepared to add `__start` to your binary, I'm assuming that when you say "without modifying main", you nonetheless are prepared to modify the binary. Is that true? – rici Jun 20 '19 at 18:41

1 Answers1

2

Modifying the C Runtime Environment's int argc argument counter before the C int main(int argc, char **argv) method is called. . . - - - = = = (; A NASTY HACK ;) = = = - - - . . .


I am using:

  • The latest C++ compiler currently available for OS X High Sierra,
  • Installed the currently standard way, using brew.

To reproduce the results below argc.cpp code listing, either:


argc.cpp :

    #include <stdio.h>

    __attribute__((constructor)) void start(int argc, char **argv)
    {
        int * pc = (int *) argv - 2; // NASTY HACK TO GET C RUNTIME argc * ;)
        printf("argc = %d \n", *pc); // the original argc, on most systems ;)
        int NUMBER_OF_PARAMETERS_NEEDED_FOR_FUZZING = 1; // Replace this line 
        // with the simple/complex logic needed for fuzz testing (fuzzing)
        if(!(argc > NUMBER_OF_PARAMETERS_NEEDED_FOR_FUZZING)){ 
            argc = NUMBER_OF_PARAMETERS_NEEDED_FOR_FUZZING + 1;
            *pc = argc; // NASTY HACK TO OVERWRITE C RUNTIME argc ;)
        } 
        // *pc = 2; // uncomment this to see that you can also reduce argc
        argv[1] = "hello"; // Example setting of a fuzzy argument
        // Add more lines, a loop, etc... here to set more fuzzy arguments 
        for (int i = 0; i < argc; i++) {
            printf("%s: argv[%d] = '%s'\n", __FUNCTION__, i, argv[i]);
        }
        printf("argc = %d \n", argc); // the possibly modified argc
    }

    int main(int argc, char **argv)
    {
        for (int i = 0; i < argc; i++) {
            printf("%s: argv[%d] = '%s'\n", __FUNCTION__, i, argv[i]);
        }
        printf("argc = %d \n", argc); // the possibly modified argc
        return 0;
    }

Mac OS Terminal:


Compilation:

    $ /usr/local/bin/c++-9 argc.cpp -o argc

Running with 0 arguments, original int argc = 1:

    $ ./argc
    argc = 1 
    start: argv[0] = './argc'
    start: argv[1] = 'hello'
    argc = 2 
    main: argv[0] = './argc'
    main: argv[1] = 'hello'
    argc = 2 

Running with 3 arguments, original int argc = 4:

    $ ./argc 1 2 3
    argc = 4 
    start: argv[0] = './argc'
    start: argv[1] = 'hello'
    start: argv[2] = '2'
    start: argv[3] = '3'
    argc = 4 
    main: argv[0] = './argc'
    main: argv[1] = 'hello'
    main: argv[2] = '2'
    main: argv[3] = '3'
    argc = 4 

Running , to demonstrate argc reduction capability,

  • after uncommenting line 13 and recompiling (that hardcodes argc = 2) :
        *pc = 2; // uncomment this to see that you can also reduce argc

with the same 3 arguments, as above, original int argc = 4:

    $ ./argc 1 2 3
    argc = 4 
    start: argv[0] = './argc'
    start: argv[1] = 'hello'
    start: argv[2] = '2'
    start: argv[3] = '3'
    argc = 4 
    main: argv[0] = './argc'
    main: argv[1] = 'hello'
    argc = 2 

If the C Runtime Environment is not initialized on your system similar to the listing:

you may need to either:

  • Research the difference, by comparing your local C Runtime Environment to the GNU sample above, and/or
  • Experimentally modify my code, until it works for you. For example: walk the int pointers before and also !after! the location of argv and see what you find there...

If you find hacking your current C Runtime Environment too hard, try to:


Here is how/why this hack works:

  1. The C Runtime Environment function applicable for your setup (there are multiple possibilities) saves the int argc, char **argv, char **envp arguments in some memory locations - these are luckily adjacent in case of my setup.
  2. On my 64 bit system, due to memory alignment rules the int argc address would be just 1 extra sizeof(int) ahead of the memory pointed to by char **argv, that is why the - 2 in the: int * pc = (int *) argv - 2; line is not - 1.

╦ ╦  ╔═╗  ╔═╗  ╔═╗  ╦ ╦       ╦ ╦  ╔═╗  ╔═╗  ╦╔═  ╦  ╔╗╔  ╔═╗ ╦ ╦ ╦
╠═╣  ╠═╣  ╠═╝  ╠═╝  ╚╦╝  ───  ╠═╣  ╠═╣  ║    ╠╩╗  ║  ║║║  ║ ╦ ║ ║ ║
╩ ╩  ╩ ╩  ╩    ╩     ╩        ╩ ╩  ╩ ╩  ╚═╝  ╩ ╩  ╩  ╝╚╝  ╚═╝ o o o
Community
  • 1
  • 1
  • 1
    Thanks! However, I don't understand why 'argc' is not shared between start and main. If we cannot modify main function, the output does not exit "1 hello". In some scenarios, such as instrument of a binary file, we cannot modify the binary file to change its 'argc'. – jackzhou Jun 20 '19 at 07:34
  • 1
    'argc' is not a pointer, so we change it in start function, which cannot affect the actual value of 'argc' of main function. Is there a way to do that? – jackzhou Jun 20 '19 at 07:42
  • 1
    Thanks for your answer. It was very helpful. However, I cannot reproduce it on my computer with x86_64 ubuntu system. I have tried the address from (int *) argv - 1 to (int *) argv - 12, the value of argc was not changed, but it is a good starting point for me to hack it. – jackzhou Jun 24 '19 at 00:49
  • Great, my pleasure, please try to also explore the int pointers after the argv, (+2, +4 etc...) If you find hacking your current C Runtime Environment too hard, try to install GNU compilers on your Ubuntu 64: [help.ubuntu.com/community/InstallingCompilers]. – Lucifer Morningstar Jun 29 '19 at 00:39