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.