Alexander Gromnitsky's Blog

Shell postcards

Latest update:

In the very distant past, there were web services for creating "virtual postcards": collections of cats, flowers, Santas, &c, sometimes animated, where you would chose a picture, write a couple of words, provide a email of a recipient, & press Submit.

I think today, when every desktop OS has a decent terminal emulator, we can sent little shell scripts instead. E.g., if Bob receives a file

$ fold -w34 postcard
tail -c+37 "$0"|base64 -d|gzip -cd
#H4sIALdOkGUAA62XO47bMBCG+1xBDbNNu
sBeCnDhA+QQrlwY2c1GyCIrJ0jHQoUKFbI
...
EkmUw04ZNvxUwGDoSYCTCG+IUYwfpg/7MA
v8BGNZ3QFsTAAA=

he won't be able to immidiately see what it is about unless he runs it:

$ chmod +x postcard
$ ./postcard
an unencrypted postcard

The "payload" in the "postcard" is modified ANSI art from 1992 (RN-BART.MIR in mirage01.zip artpack), converted to UTF-8.

We can generate such a program with a simple script that expects raw payload form stdin:

$ cat mkpostcard
#!/bin/sh

set -e
prefix() { printf 'tail -c+37 "$0"|base64 -d|gzip -cd\n#'; }

script=`mktemp`
trap 'rm -f "$script"' 1 2 15

prefix > "$script"
gzip -c | base64 -w0 >> "$script"
chmod +x "$script"
mv "$script" "${1:-postcard}"

Easy-peasy. We are not adding a shebang, as in this case, it may be omitted.

We can also go further & generate a password-protected postcard. This presents some difficulties, though: POSIX doesn't mention any command line utility for (en|de)cryption, & openssl could be missing on a target machine. Thus we either

  • write a xor-cipher in awk or
  • embed a source code of a C implementation of ChaCha20, but again, the target machine may not have a compiler installed or
  • embed an αcτµαlly pδrταblε εxεcµταblε what will take care of decryption.

Everybody has heard about Cosmopolitan Libc toolchain during the Covid19 days, but personally, I haven't had any use for it.

It's an amazing peace of work. E.g., this minimal rc4 cipher implementation (I believe it's public domain)

$ cat rc4.c
#define S ,t=s[i],s[i]=s[j],s[j]=t /* rc4 hexkey <file */
unsigned char k[256],s[256],i,j,t;main(c,v,e)char**v;{++v;while(++i)s[
i]=i;for(c=0;*(*v)++;k[c++]=e)sscanf((*v)++-1,"%2x",&e);while(j+=s[i]
+k[i%c]S,++i);for(j=0;c=~getchar();putchar(~c^s[t+=s[i]]))j+=s[++i]S;}

compiles into

$ file rc4
rc4: DOS/MBR boot sector; partition 1 : ID=0x7f, active, start-CHS (0x0,0,1), end-CHS (0x3ff,255,63), startsector 0, 4294967295 sectors
$ du rc4
404K    rc4

that runs on Linux (including aarch64, I specifically checked that!) and FreeBSD.

rc4.c has a few deficiencies:

  • if its argv[1] is null or an empty string, it coredumps;
  • it requires a hex-formatted string, not a plain-text password.

At first, I fixed the bug, & added an str2hex conversion. I could continue & add everything else: embed an encrypted message, read the password from terminal, &c, but why? Writing shell scrips is easier & a postcard recipent then gets a text file, not a binary1. Hence, I reverted to the "stock" rc4.c, & decided to use Ruby's erb instead:

$ cat message.erb
#<%= rc4=File.read(rc4) %>
#<%= message=File.read(message) %>
slice() { tail -c+$1 "$0" | head -c $2 | base64 -d | gzip -cd; }
cleanup() { rm -f "$rc4"; stty echo; }
set -e
rc4=`mktemp`
trap cleanup 0
trap 'cleanup; echo 1>&2; exit 1' 1 2 15
slice 2 '<%= rc4.length %>' > "$rc4"
chmod +x "$rc4"
stty -echo
printf 'Password: ' 1>&2; read -r password; printf "\n" 1>&2
[ -n "$password" ]
hexkey=`printf '%s' "$password" | od -A n -t x1 -v | tr -d ' \n'`
slice '<%= rc4.length+4 %>' '<%= message.length %>' | "$rc4" $hexkey

It generates a "password-protected" shell script, that

  1. extracts the αcτµαlly pδrταblε rc4 εxεcµταblε from its 1st comment line;
  2. asks a user for a password;
  3. converts the password to a hex string;
  4. extracts the "message" from its 2nd comment line and decrypts it with rc4.

Makefile, that assists in creating such a script:

password := monkey
message := payload/hello1.txt
out := _out
CC := ~/opt/s/cosmocc/bin/cosmocc

all: $(out)/message

$(out)/%: %.c
    @mkdir -p $(dir $@)
    $(CC) --std=c89 -o $@ $<

$(out)/message: message.erb $(out)/rc4
    gzip -c < $(out)/rc4 | base64 -w0 > rc4.text
    $(out)/rc4 $(hexkey) < $(message) | gzip -c | base64 -w0 >message.text
    erb rc4=rc4.text message=message.text $< > $@
    chmod +x $@
    rm *.text

.DELETE_ON_ERROR:
hexkey = $(shell printf '%s' $(call se,$(password)) | od -A n -t x1 -v | tr -d ' \n')
se = '$(subst ','\'',$1)'

You'll need to adjust CC variable which expects the compiler from Cosmopolitan Libc toolchain.

Then

$ make password=12345 message=payload/TO-GZ.txt
...
$ du _out/message
304K    _out/message
$ _out/message
Password:
a pixelated dog

I should end with a remainder that rc4 is not a moderd, state-of-the-art cipher, & you should absolutely not blindly run "encrypted" postcards from unknown sources.


  1. Whilst αcτµαlly pδrταblε εxεcµταblεs are also shell scripts that cleverly pretend otherwise, they are not plain-text files.

Tags: ойті
Authors: ag