10

I have to test a hash function and I want to change only a single bit of a specific file.

I tried with the dd command. That works, but I can only change a whole byte and not just a bit.

sudo dd if=/dev/zero of=/file.bin bs=1 seek=10 count=1 conv=notrunc

I also tried the sed command with a regex, but as I don't know the content of the file, I can't just change an "a" to a "b".

Does anyone know a command for doing this?

7
  • 1
    If you can find a lower case character (ascii, or single byte utf8) and convert to upper case, or visa versa. Then this is a 1 bit change. This is not a very good solution hence just a comment. Apr 14, 2015 at 20:29
  • Yes it's probably the best option, but can I change only the first occurence ? And I can get a result in case of success/not found ? Maybe it's possible with a XOR mask on the file directly ?
    – Kantium
    Apr 14, 2015 at 20:34
  • How do you determine which bit to change? Is your goal to toggle a bit at a specific offset? And do you change 0 to 1 and 1 to 0, or (say) do you always set it to 1? Finally: What kind of data does the file contain? Could it be "binary", i.e. including null bytes?
    – alexis
    Apr 14, 2015 at 20:45
  • Ideally, toggle a random or fixed bit is excellent, but if I can already force a bit to zero or one is also good for me. And yes it's a binary file
    – Kantium
    Apr 14, 2015 at 20:47
  • 2
    I'd just write a program to read the byte containing the selected bit position, construct a byte differing in just the one bit, and write it out. Since file length does not change, you only need to read and write that one byte.
    – alexis
    Apr 14, 2015 at 20:59

6 Answers 6

3

Since the file may contain nulls, text-oriented filters like sed are going to fail. But you can use a programming language that can handle nulls, like perl or python. Here's a solution for Python 3. It's a few lines longer than strictly necessary, for readability.

#!/usr/bin/env python3
"""Toggle the bit at the specified offset.
Syntax: <cmdname> filename bit-offset"""

import sys
fname = sys.argv[1]
# Convert bit offset to bytes + leftover bits
bitpos = int(sys.argv[2])
nbytes, nbits = divmod(bitpos, 8)

# Open in read+write, binary mode; read 1 byte 
fp = open(fname, "r+b")
fp.seek(nbytes, 0)
c = fp.read(1)

# Toggle bit at byte position `nbits`
toggled = bytes( [ ord(c)^(1<<nbits) ] ) 
# print(toggled) # diagnostic output

# Back up one byte, write out the modified byte
fp.seek(-1, 1)  # or absolute: fp.seek(nbytes, 0)
fp.write(toggled)
fp.close()

Save it in a file (e.g., bitflip), make it executable, and run it with the filename to modify and the offset in bits. Note that it modifies the file in place. Run it twice with the same offset and you'll get your file restored.

3
  • Works like a charm, and it's smart
    – Kantium
    Apr 14, 2015 at 22:18
  • 1
    This does not work if running in Python2 (Author should use /usr/bin/env python3 for clarity! Create file with 00000 (ascii). Change bit 2. The output should only change one character but changes 4. Make sure to run in Python 3 and not Python 2. Apr 7, 2020 at 7:42
  • Thanks for the suggestion, I changed the shebang. (The text did already say that the solution is "for Python 3", but this is better.)
    – alexis
    Apr 7, 2020 at 23:20
3

I don't think there's a single command. Here's a simple script, save it as "flipbit":

#!/usr/bin/perl
# Arguments:   byte (starting from 0),  bit (0-7),  filename (otherwise stdin)
$byte = shift(@ARGV);
$bit = shift(@ARGV);
undef $/; 
$file=<>; 
substr($file,$byte,1) = substr($file,$byte,1) ^ chr(1<<$bit); 
print $file;

test:

$ echo abb | ~/bin/flip-bit.pl 2 0 | od -xa
0000000      6261    0a63                                                
       a   b   c  nl                                                

this flipped the low-order bit (0) of the third character, changing the 'b' to 'c'.

As a single line command:

perl -e '$byte=shift(@ARGV);$bit=shift(@ARGV);undef $/; $file=<>; substr($file,$byte,1) = substr($file,$byte,1) ^ chr(1<<$bit); print $file'
1
  • Hint: You need to redirect stdout into a file to have the result saved. Example: perl flipbit.pl 1000000 1 data.tgz > data_corrupt.tgz Feb 26, 2016 at 22:00
3

Finally I found a solution with xxd and dd.

a=$(xxd -b -l 1 -seek 3 -p a.bin);b=1;echo -e "\x$((${a}^${b}))" | dd of=a.bin bs=1 seek=3 count=1 conv=notrunc

hexdump a.bin     v
0000000 61 39 73 36 36 64 66 38 61 39 73 64 35 36 66 35
0000010 37 61 73 64 37 66 74 75 61 67 73 0a 61 73 64 66

hexdump b.bin     v
0000000 61 39 73 37 36 64 66 38 61 39 73 64 35 36 66 35
0000010 37 61 73 64 37 66 74 75 61 67 73 0a 61 73 64 66

But this is ugly.

2
  • I got a bash error for a byte with leading '0' so I changed the XOR part to echo -e "\x$((${a#0}^${b}))"
    – MattSmith
    Dec 19, 2017 at 0:28
  • Sorry, it should be a 0x${a}
    – MattSmith
    Dec 19, 2017 at 3:05
1

If you really want to use dd, here is an abomination that will do the trick by flipping the highest bit in the given byte. Adjust the settings for the tr command to change the selected bit.

# Preparation
finger > original.txt
BYTE=3

# Here we go...
dd if=original.txt bs=1c 2>/dev/null | ( dd bs=1c count=$((BYTE-1)) ; dd bs=1c count=1 | tr '\000-\377' '\200-\377\000-\177' ; dd bs=1c ) 2>/dev/null > flipped.txt

# Demonstrate the difference (byte 3: 67 → e7)
hexdump -C original.txt | head -1
00000000  4c 6f 67 69 6e 20 20 20  20 20 4e 61 6d 65 20 20  |Login     Name  |    
hexdump -C flipped.txt | head -1
00000000  4c 6f e7 69 6e 20 20 20  20 20 4e 61 6d 65 20 20  |Lo.in     Name  |
0

Simple solution using head, tail, xxd. Example below flips least significant bit in last byte of file.bin.

head -c -1 file.bin > flipped.bin
LAST=`tail -c 1 file.bin | xxd -ps`
printf "%02X" $(( $((16#$LAST)) ^ 1 )) | xxd -r -ps >> flipped.bin
0

Here are two perl one-liners. The first changes the file bar in-place:

perl -p -0777 -i -e 'substr($_,2,1)^=chr(1<<5)' bar

The second reads file foo and writes bar

perl -p -0777 -e 'substr($_,2,1)^=chr(1<<5)' < foo > bar

To adapt to your application: the 2 selects which byte: 0..file_length-1, i.e. 2 is the 3rd byte. The 5 selects which bit to flip: 0-7, i.e 5 is the 6th bit. This will only work for files that fit in memory.

Explanation

-p iterate through the file, print after each iteration
-0777 read the whole file into memory on each iteration (so there will only be one iteration)
-e run the following perl code inside the loop
substr select a single character in the file starting at index 2
^=chr XOR that character with 1 shifted 5 times i.e. 2^5

This answer is a simplified version of toddkaufmann's

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .