Author: @Kkevsterrr
This bin looks locked as a crypt to me, but I’m sure you can work some magic here. All I know is that this came from a machine with a cryptic ID of just ‘hello’.
We are given flag.sh
, a crazily obfuscated bash script with a lot of unprintable characters. However, running strings
on it helped me notice something useful
❯ strings flag.sh | head -n 30
#!/bin/sh
';eval`:||
` "`:||
`$(`:||
`ec`#
`ho `:||
|LANG=C perl`:||
` -pe`#
` "s`#
`/[^`:||
`[`#
`:pr`:||
`in`#
`t:`#
`]]`:||
`//g"`#
`|`#
`ope`:||
`n`#
`ssl `:||
`ba`:||
`se`:||
`64 -`:||
`A -`:||
`d`#
`)`:||
#WM_5
`]ky
B315\
}sn.cQ
assuming we can ignore the :||
, we get the command
:
is just the null operator in bash which always returns true, and its being ORed by||
so we can infact ignore it
eval $(echo | LANG=C perl -pe "s/[^[:print:]]//g" | openssl base64 -A -d")
What does this look like to be doing is echoing something into a perl command which will remove all non-printable characters and base64 decode the output. The perl
command was genius and I immediately applied it to flag.sh
to get rid of the non-printable characters.
cat flag.sh | LANG=C perl -pe "s/[^[:print:]]//g"
Before I show you the output I also want to bring to your attention the start of a comment after the command #WM_5...
. This will be very important later.
In the output we get a massive base64 encoded string, which when decoded gives us another, more complex bash script shown below. Understanding this script is crucial as there is a lot going on.
Lets break it down. First we are given a bunch of global variables
BCL='aWQgLXUK'
BCV='93iNKe0zcKfgfSwQoHYdJbWGu4Dfnw5ZZ5a3ld5UEqI='
P=llLvO8+J6gmLlp964bcJG3I3mY27I9ACsJTvXYCZv2Q=
S='lRwuwaugBEhK488I'
C=3eOcpOICWx5iy2UuoJS9gQ==
Our first function bcl_verify_dec()
tests the decryption of $BCV
1_bcl_verify_dec ()
2{
3 [ "TEST-VALUE-VERIFY" != "$(echo "$BCV" | openssl enc -d -aes-256-cbc -md sha256 -nosalt -k "B-${1}-${UID}" -a -A 2> /dev/null)" ] && return 255;
4 echo "$1-${UID}"
5}
It’s doing aes decryption with the key B-${1}-${UID}
where ${1}
is the argument passed to the function, and $UID
is the UID of the user.
bcl_verify_dec
is being called by _bcl_verify
which is in turn being called by _bcl_get
and just forwarding its arguments
1_bcl_get ()
2{
3 [ -z "$UID" ] && UID="$(id -u 2> /dev/null)";
4 [ -f "/etc/machine-id" ] && _bcl_verify "$(cat "/etc/machine-id" 2> /dev/null)" && return;
5 command -v dmidecode > /dev/null && _bcl_verify "$(dmidecode -t 1 2> /dev/null | LANG=C perl -ne '/UUID/ && print && exit')" && return;
6 _bcl_verify "$({ ip l sh dev "$(LANG=C ip route show match 1.1.1.1 | perl -ne 's/.*dev ([^ ]*) .*/\1/ && print && exit')" | LANG=C perl -ne 'print if /ether / && s/.*ether ([^ ]*).*/\1/'; } 2> /dev/null)" && return;
7 _bcl_verify "$({ blkid -o export | LANG=C perl -ne '/^UUID/ && s/[^[:alnum:]]//g && print && exit'; } 2> /dev/null)" && return;
8 _bcl_verify "$({ fdisk -l | LANG=C perl -ne '/identifier/i && s/[^[:alnum:]]//g && print && exit'; } 2> /dev/null)" && return
9}
_bcl_verify
is being called with the machine-id
of the machine, which is where the description comes into picture (its being called with other stuff too, but I’m going to ignore that for now)
From the description we can infer that the machine id is hello
, which leaves the matter of the UID
. Here my teammate managed to brute force the UID to get the correct decrypted string, lets try it out
1#!/bin/bash
2
3BCV='93iNKe0zcKfgfSwQoHYdJbWGu4Dfnw5ZZ5a3ld5UEqI='
4i=0
5while [ "TEST-VALUE-VERIFY" != "$(echo "$BCV" | openssl enc -d -aes-256-cbc -md sha256 -nosalt -k "B-hello-${i}" -a -A 2> /dev/null)" ]; do
6 ((i++))
7done
8
9echo $i
which gives us UID=1338
. We can now move on. Next using the P
variable above we generate a password _P
1_bcl_gen_p ()
2{
3 local _k;
4 local str;
5 [ -z "$BC_BCL_TEST_FAIL" ] && _k="$(_bcl_get)" && _P="$(echo "$1" | openssl enc -d -aes-256-cbc -md sha256 -nosalt -k "$_k" -a -A 2> /dev/null)";
6 [ -n "$_P" ] && return 0;
7 [ -n "$fn" ] && {
8 unset BCL BCV _P P S fn;
9 unset -f _bcl_get _bcl_verify _bcl_verify_dec;
10 return 255
11 };
12 BCL="$(echo "$BCL" | openssl base64 -d -A 2> /dev/null)";
13 [ "$BCL" -eq "$BCL" ] 2> /dev/null && exit "$BCL";
14 str="$(echo "$BCL" | openssl base64 -d -A 2> /dev/null)";
15 BCL="${str:-$BCL}";
16 exec /bin/sh -c "$BCL";
17 exit 255
18}
The key for this decryption is coming from _bcl_get
which in turns passes the echo
value of _bcl_verify_dec
which is ${1}-${UID}
or hello-1338
Using that key we can get the password _P
❯ echo "llLvO8+J6gmLlp964bcJG3I3mY27I9ACsJTvXYCZv2Q=" | openssl enc -d -aes-256-cbc -md sha256 -nosalt -k "hello-1338" -a -A 2> /dev/null
QHh4K9JfgoACd2f4 # -> _P
We can ignore the rest of the function (and the value of BCL
), all it does print your own UID and stop the script if you didn’t manage to decrypt P
This value of _P
along with the global S
are now used to decrypt C
which is then eval
ed
_P=QHh4K9JfgoACd2f4
S='lRwuwaugBEhK488I'
C=3eOcpOICWx5iy2UuoJS9gQ==
eval $(echo "$C" | openssl enc -d -aes-256-cbc -md sha256 -nosalt -k "C-${S}-${_P}" -a -A 2>/dev/null)
# R=2105
C
=R=2105
, which is eval
ed so we now have a new variable R
equal to 2105
Finally we reach our main decryption logic, depending on the way the script was executed we enter different code branches, but they all do the same thing.
eval "unset _P S R fn;$(LANG=C perl -e '<>;<>;read(STDIN,$_,1);while(<>){s/B3/\n/g;s/B1/\x00/g;s/B2/B/g;print}'<"${fn}"|openssl enc -d -aes-256-cbc -md sha256 -nosalt -k "${S}-${_P}" 2>/dev/null|LANG=C perl -e "read(STDIN,\$_, ${R:-0});print(<>)"|gunzip)"
Let’s start with the perl command
|
|
What this is doing is reading the first 2 lines and 1 byte of the 3rd line of the script. Remember the comment I highlighted earlier? This is the chunk in the comment
In that chunk it substitute B3
with \n
, B1
with \x00
and B1
with B
and pipes it to openssl
openssl enc -d -aes-256-cbc -md sha256 -nosalt -k "${S}-${_P}" 2>/dev/null
This takes the resulting output and decrypts it with the key lRwuwaugBEhK488I-QHh4K9JfgoACd2f4
, then pipes that into another perl
command
|
|
which reads an R
( = 2105 ) amount of bytes then pipes the rest into gunzip. The final result is an unzipped file containing the flag!
While solving the challenge I wasn’t really sure about what the perl commands were doing, but I was suspicious of the content of the comment mentioned earlier, and I could see that the B
substitutions were occurring and that something was being unzipped, so I did
❯ cat meow | LANG=C perl -pe 's/B3/\n/g;s/B1/\x00/g;s/B2/B/g' | openssl enc -d -aes-256-cbc -md sha256 -nosalt -k "lRwuwaugBEhK488I-QHh4K9JfgoACd2f4" 2>/dev/null > zipped_file
where meow
contained only the comment chunk. Running binwalk
on the zipped_file
gave me the flag :)
❯ binwalk -e zipped_file
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
2105 0x839 gzip compressed data, from Unix, last modified: 2025-05-21 18:53:35
Exact 2105 byte offset
❯ cat _zipped_file.extracted/839
echo "flag{f2ea4caf879bde891f0174f528c20682}"
echo "Congraulations!"