rev

Challenges

Plastic Shield

OPSec is useless unless you do it correctly.

View Hint: Hint 1

The algorithm implementation itself is not the problem, I would look elsewhere.

strings plastic-shield, and you can have some good observation about the output
PTE1
H=PpA
ATSH
[A\]
ATSH
[A\]
@@H1
@HH1
/lib64/ld-linux-x86-64.so.2
free
putchar
strtol
strlen
malloc
__libc_start_main
sprintf
__isoc99_scanf
libc.so.6
GLIBC_2.7
GLIBC_2.34
GLIBC_2.2.5
__gmon_start__
Please enter the password: 
%255s
%02x
713d7f2c0f502f485a8af0c284bd3f1e7b03d27204a616a8340beaae23f130edf65401c1f99fe99f63486a385ccea217
Decrypted text: 
c|w{
9JLX
~=d]
lpHP
expand 32-byte k
1	,k
;*3$"
GCC: (GNU) 15.1.1 20250521 (Red Hat 15.1.1-2)
AV:4p1294
RV:running gcc 15.1.1 20250521
BV:annobin gcc 15.1.1 20250521
GW:0x7d60562 ../sysdeps/x86/abi-note.c
SP:3
SC:1
CF:8 ../sysdeps/x86/abi-note.c
FL:0 ../sysdeps/x86/abi-note.c
GA:1
PI:4
SE:0
iS:0
GW:0x7d60562 init.c
CF:8 init.c
FL:0 init.c
GW:0x7d60562 static-reloc.c
SP:0 static-reloc.c
CF:8 static-reloc.c
FL:0 static-reloc.c
crt1.o
__abi_tag
crtbegin.o
deregister_tm_clones
__do_global_dtors_aux
completed.0
__do_global_dtors_aux_fini_array_entry
frame_dummy
__frame_dummy_init_array_entry
plastic-shield.c
aes.c
rsbox
Rcon
KeyExpansion
AddRoundKey
xtime
InvMixColumns
InvSubBytes
InvShiftRows
InvCipher
XorWithIv
monocypher.c
load24_le
load32_le
load64_le
store32_le
store64_le
load32_le_buf
load64_le_buf
store32_le_buf
store64_le_buf
rotr64
rotl32
neq0
chacha20_rounds
chacha20_constant
poly_blocks
blake2b_compress
sigma.8
blake_update_32
blake_update_32_buf
copy_block
xor_block
extended_hash
g_rounds
fe_one
sqrtm1
lop_x
lop_y
ufactor
fe_0
fe_1
fe_copy
fe_neg
fe_add
fe_sub
fe_cswap
fe_ccopy
fe_frombytes_mask
fe_frombytes
fe_tobytes
fe_mul_small
fe_mul
fe_sq
fe_isodd
fe_isequal
invsqrt
fe_invert
scalar_bit
scalarmult
base_point.7
multiply
is_above_l
remove_l
mod_l
ge_zero
ge_tobytes
ge_frombytes_neg_vartime
ge_cache
ge_add
ge_sub
ge_madd
ge_msub
ge_double
b_window
slide_init
slide_step
zero_point.5
b_comb_low
b_comb_high
lookup_add
ge_scalarmult_base
half_ones.3
half_mod_L.4
hash_reduce
add_xl
dirty_base_point.2
select_lop
redc
Lm2.0
lock_auth
crtend.o
__FRAME_END__
_DYNAMIC
__GNU_EH_FRAME_HDR
_GLOBAL_OFFSET_TABLE_
free@GLIBC_2.2.5
putchar@GLIBC_2.2.5
__libc_start_main@GLIBC_2.34
crypto_blake2b_keyed
crypto_verify16
crypto_x25519_dirty_fast
AES_CBC_encrypt_buffer
crypto_eddsa_reduce
crypto_aead_init_x
crypto_eddsa_sign
crypto_aead_write
crypto_argon2_no_extras
crypto_chacha20_h
_edata
crypto_chacha20_x
crypto_blake2b_init
AES_CBC_decrypt_buffer
crypto_blake2b_update
_fini
crypto_chacha20_ietf
strlen@GLIBC_2.2.5
crypto_verify64
crypto_poly1305_final
crypto_eddsa_scalarbase
AES_init_ctx_iv
crypto_elligator_map
AES_ECB_decrypt
AES_init_ctx
crypto_elligator_rev
crypto_chacha20_djb
crypto_x25519_dirty_small
AES_ctx_set_iv
crypto_x25519
__data_start
crypto_blake2b_keyed_init
crypto_blake2b_final
crypto_blake2b
crypto_elligator_key_pair
crypto_poly1305_init
AES_CTR_xcrypt_buffer
__gmon_start__
strtol@GLIBC_2.2.5
__dso_handle
_IO_stdin_used
crypto_poly1305_update
crypto_argon2
malloc@GLIBC_2.2.5
_end
_dl_relocate_static_pie
crypto_x25519_public_key
crypto_aead_init_ietf
crypto_aead_init_djb
crypto_eddsa_to_x25519
crypto_aead_lock
__bss_start
main
crypto_eddsa_trim_scalar
crypto_x25519_to_eddsa
crypto_poly1305
hex_to_bytes
__isoc99_scanf@GLIBC_2.7
crypto_eddsa_mul_add
crypto_eddsa_key_pair
crypto_verify32
sprintf@GLIBC_2.2.5
crypto_aead_read
__TMC_END__
crypto_x25519_inverse
crypto_eddsa_check
crypto_aead_unlock
AES_ECB_encrypt
crypto_eddsa_check_equation
crypto_wipe
.symtab
.strtab
.shstrtab
.note.gnu.build-id
.init
.text
.fini
.interp
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rela.plt
.rodata
.eh_frame_hdr
.eh_frame
.note.gnu.property
.note.ABI-tag
.init_array
.fini_array
.dynamic
.got
.got.plt
.data
.bss
.comment
.annobin.notes
.gnu.build.attributes
  • This is after I throw it into dogbolt, and check the main of the program

  • Only one character is extracted: char var_189 = var_148[rdx >> 2];

  • That character goes directly to blake2b: crypto_blake2b(&var_188, 0x40, &var_189, 1);


  • Figedting some more then I have to look at Ghidra

  • 0x3c in hexadecimal = 60 in decimal

  • So: local_30 = (password_length * 60) / 100

  • Which simplifies to: local_30 = password_length * 0.6

With all that in places the binary plastic-shield is a password-checking program that:

  1. Asks for a password input (up to 255 characters)

  2. Applies a special character detection algorithm - it finds the character at position floor(0.6 * password_length)

  3. Uses that special character as a key to decrypt an embedded ciphertext

  4. Uses BLAKE2b hashing to derive a 64-byte hash from the special character

  5. Uses AES-CBC decryption with:

    • Key: first 32 bytes of the hash

    • IV: bytes 32-48 of the hash

  6. And we need to decrypts from the embedded hex string 713d7f2c0f502f485a8af0c284bd3f1e7b03d27204a616a8340beaae23f130edf65401c1f99fe99f63486a385ccea217

Script

from binascii import unhexlify
from hashlib import blake2b
from Crypto.Cipher import AES


def main():
    # The embedded hex ciphertext from the binary
    hex_ciphertext = "713d7f2c0f502f485a8af0c284bd3f1e7b03d27204a616a8340beaae23f130edf65401c1f99fe99f63486a385ccea217"
    ciphertext = unhexlify(hex_ciphertext)

    print(f"[*] Brute forcing special character...")

    # Try all printable ASCII characters
    for i in range(32, 127):
        char = chr(i)

        # Hash the character with BLAKE2b (64 bytes)
        hash_result = blake2b(char.encode("utf-8"), digest_size=64).digest()

        # Split: first 32 bytes = key, next 16 bytes = IV
        key = hash_result[:32]
        iv = hash_result[32:48]

        # Decrypt with AES-CBC
        cipher = AES.new(key, AES.MODE_CBC, iv)
        plaintext = cipher.decrypt(ciphertext)

        # Remove PKCS7 padding
        pad_len = plaintext[-1]
        if 1 <= pad_len <= 16:
            plaintext = plaintext[:-pad_len]

        # Check if it looks like a flag
        decoded = plaintext.decode("utf-8", errors="ignore")
        if "scriptCTF{" in decoded:
            print(f"[+] Found special character: '{char}' (ASCII {i})")
            print(f"[+] Flag: {decoded}")

            # Generate example password
            password_length = 10
            special_pos = int(0.6 * password_length)  # Position 6 for length 10
            password = ["A"] * password_length
            password[special_pos] = char
            example_password = "".join(password)

            print(f"[+] Example working password: {example_password}")
            return

    print("[-] No flag found!")

main()
❯ python solve.py
[*] Brute forcing special character...
[+] Found special character: '`' (ASCII 96)
[+] Flag: scriptCTF{20_cau541i71e5_d3f3n5es_d0wn}
[+] Example working password: AAAAAA`AAA
  • scriptCTF{20_cau541i71e5_d3f3n5es_d0wn}

Plastic Shield 2 (upsolve)

Okay! Fixed last time's issue. Seriously though, I swear this one is unbreakable.

tbd, will write later.

Last updated