Passing special character as argument

Viewed 1932

I need to pass a string whose hex is 00 2C 00 21 to my program as the command line argument which i am unable to do.

#include<stdio.h>
int main(int argc,char* argv[]){

// argv[1] should have the string that the above hex represents 

//... the program will use that string inside the program

//...also please explain what should i do if i (am/am not) allowed to modify the source  

}

Since 00 is NULL character i am unable to represent it in command line and pass it to the program. Also i need to pass string composed of various other characters whose hex values are like 01 or 02 (for eg) which you cannot enter directly from the keyboard and pass as an argument.

What should i do so that my program receives the string whose hex representation is 00 2C 00 21.

$./a.out " what should i write here?  " 
2 Answers

You should make your program accept a string with escapes in it, and parse them yourself. So it would be invoked like so:

$ ./myprogram '\x00\x2c\x00\x21'

for instance (the \x matches what C itself uses, so can be familiar to users). The single quotes are to protect the backslashes from the shell, not 100% sure and not at a proper prompt right now.

The result won't be a string, since strings in C cannot contain 0-characters.

Here's an example of how this could look:

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

static size_t decode(void *buf, size_t buf_max, const char *s)
{
    unsigned char *put = buf;
    unsigned char * const put_max = put + buf_max;
    while (*s != '\0' && put != put_max)
    {
        if (*s == '\\')
        {
            ++s;
            if (*s == '\\')
                *put++ = *s++;
            else if (*s == 'x')
            {
                ++s;
                char *endp;
                const unsigned long v = strtoul(s, &endp, 16);
                if (endp == s)
                    break;
                *put++ = (unsigned char) v;
                s = endp;
            }
            else
                break;
        }
        else
            *put++ = *s++;
    }
    return put - (unsigned char *) buf;
}

int main(int argc, char *argv[])
{
    unsigned char buf[32];
    const size_t len = decode(buf, sizeof buf, "\\x0hello\\x1\\xaa\\xfe\\xed");
    for (size_t i = 0; i < len; ++i)
    {
        printf("%x\n", buf[i]);
    }
    return 0;
}

Note that the test "driver" in main() would be replaced in your case, you want to pass e.g. argv[1] to decode(). The double backslashes protect against the C compiler, we really want to end up with a string containing backslash escapes.

It is not possible to pass zero byte to a program argument using bash or any other shell. This is just because it is not possible in the C standard.

C standard says C11 5.1.2.2.1p2 (emphasis mine):

... the parameters to the main function shall obey the following constraints:
- ...
- If the value of argc is greater than zero, the array members argv[0] through argv[argc-1] inclusive shall contain pointers to strings, which are given implementation-defined values by the host environment prior to program startup. ...
- ...

A "string" is C11 7.1.1p1 (emphasis mine):

A string is a contiguous sequence of characters terminated by and including the first null character. ... The length of a string is the number of bytes preceding the null character and the value of a string is the sequence of the values of the contained characters, in order.

The "null character" is a byte with all bits set to 0 C11 5.2.1p2. It is a zero. On the first "null character" the string terminates. If an array of characters has embedded zero bytes in it, it could not be a string (heh, in the exact sense, see note 78, a string literal may not be a string, because it can have embedded null characters). You cannot pass multiple 0x00 values embedded in arguments to a C program, as that wouldn't be a "string" that you are passing.

The proper way it to write your own parser around it, that will accept "strings" (ie. ./a.out "00 2C 00 21") and convert to zero bytes yourself.

For your use case, if it is simple, I could present a simpler parser then in the other answer. You could ex. pass an argument with all bytes incremented by 1, then decrement by 1 in your program.

Or you could pass special byte value, like ex. 0xff (if your implementation and operating system and environment supports passing 0xff bytes) in place of 0x00, and replace them in your program. This option is presented below:

#include <string.h>
#include <stddef.h>
#include <assert.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
   assert(argc >= 2);
   for (size_t i = 0, max = strlen(argv[1]); i < max; ++i) {
        // replace all 0xff by 0x00
        if ( (0xff & argv[1][i]) == 0xff) {
           argv[1][i] = 0x00;
        }
   }
   // use argv[1]
   for (size_t i = 0, max = 4; i < max; ++i) { 
       printf("argv[1][%d] = 0x%02x\n", i, 0xff & argv[1][i]);
   } 
}

and call with:

./a.out $'\xff\x2c\xff\x2c'

Tested on repl.it.

The $'...' is interpreted by bash as ANSI-C Quoting. The \xff are interpreted as hex constants, so the first argument will be equal to (char[]){0xff, 0x2c, 0xff, 0x2c, 0x00}. After you substitute 0xff for 0x00, it will become (char[]){0x00, 0x2c, 0x00, 0x2c, 0x00} and you can use the first 4 bytes.

Related