Skip to main content
  1. CTF Writeups/
  2. NahamCon 2025 CTF/

Its Locked

·
Rev 453pt 98 Solves Perl Obfuscation
subzcuber
Author
subzcuber
i like to imagine i’m funny

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 evaled

_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 evaled 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

1
2
3
4
5
6
7
8
<>;
<>;
read(STDIN,$_,1);
while(<>) {
  s/B3/\n/g;
  s/B1/\x00/g;
  s/B2/B/g;print
} < "${fn}" # fileName

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

1
2
read(STDIN,\$_, ${R:-0});
print(<>);

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!"
Reply by Email

Related

Baby Rev
Rev 50pt 233 Solves Obfuscation
python obfuscation
Deflation Gangster
Rev 364pt 166 Solves
Blue Magic
FlagsFlagsFlags
Rev 97pt 285 Solves Upx Pwntools
100k flags 💀