NahamCon CTF 2024

https://ctftime.org/event/2364



CHALL’S SOLVED

CategoryChallenge
Reverse EngineeringIPromise
Reverse EngineeringTaylor’s First Swift
Reverse EngineeringRing Cycle 1 - Basics
Web ExploitationiDoor
Web ExploitationAll About Robots
WarmupsQRRRRRRRR
WarmupsEICAR
WarmupsThat’s Not My Base
WarmupsUriel
WarmupsTwine

Reverse Engineering

IPromise

Overview

Author: @soups71

Description: Instead of making the next IPhone, I made this challenge. I do make a truthful promise though…

IPromise

First rev category challenge, let’s try to execute the binary first

$ ./IPromise
I promise that I do some decryption! You just have to find out where. Writing code shouldn't be necessary ;)

All we have to do is decompile the binary according to the output that has been given. But when I first checked using gdb, I saw that it had a lot of functions.

$ gdb ./IPromise
gef➤  info functions
All defined functions:

Non-debugging symbols:
0x0000000000401000  _init
0x0000000000401040  puts@plt
0x0000000000401050  main
0x0000000000401065  decryptIPromise
0x00000000004010d0  _start
0x0000000000401100  _dl_relocate_static_pie
0x0000000000401110  deregister_tm_clones
0x0000000000401140  register_tm_clones
0x0000000000401180  __do_global_dtors_aux
0x00000000004011b0  frame_dummy
0x00000000004011b6  KeyExpansion
0x000000000040124e  AddRoundKey
0x0000000000401285  xtime
0x0000000000401294  Cipher
0x0000000000401405  InvCipher
0x0000000000401633  AES_init_ctx
0x000000000040163c  AES_init_ctx_iv
0x000000000040165d  AES_ctx_set_iv
0x000000000040166c  AES_ECB_encrypt
0x0000000000401678  AES_ECB_decrypt
0x0000000000401684  AES_CBC_encrypt_buffer
0x00000000004016e4  AES_CBC_decrypt_buffer
0x0000000000401740  AES_CTR_xcrypt_buffer
0x00000000004017c8  _fini

Here’s the decompile of decryptIPromise function

decompiled decryptIPromise
__int64 decryptIPromise()
{
  __int128 v1; // [rsp+0h] [rbp-D8h] BYREF
  char v2[200]; // [rsp+10h] [rbp-C8h] BYREF

  v1 = xmmword_4022B0;
  AES_init_ctx(v2, &v1);
  AES_ECB_decrypt(v2, &encrypted);
  AES_ECB_decrypt(v2, &unk_404050);
  return AES_ECB_decrypt(v2, &unk_404060);
}

This function initializes an AES context with a key, and then performs AES decryption in ECB mode on three blocks of data.

Then, the key initialization is performed by AES_init_ctx function calls KeyExpansion to expand the provided key.

decompiled AES_init_ctx
char __fastcall AES_init_ctx(_BYTE *a1, __int64 a2)
{
  return KeyExpansion(a1, a2);
}

The KeyExpansion function expands the key for AES encryption/decryption, this function takes the key provided (a2) and generates the expanded key used in AES operations.

decompiled KeyExpansion
char __fastcall KeyExpansion(_BYTE *a1, __int64 a2)
{
// Key expansion logic
}

The AES_ECB_decrypt function is responsible for the actual decryption

decompiled AES_ECB_decrypt
__int64 __fastcall AES_ECB_decrypt(__int64 a1, __int64 a2)
{
  return InvCipher(a2, a1);
}

The InvCipher function performs the inverse AES cipher operation

decompiled InvCipher
__int64 __fastcall InvCipher(__int64 a1, __int64 a2)
{
  // Inverse AES cipher logic
}

We need to extract the necessary values such as xmmword_4022B0 & encrypted, let’s dissasembly with gdb > set breakpoint to decryptIPromise > run

$ gdb ./IPromise
gef➤  b decryptIPromise
Breakpoint 1 at 0x401065
gef➤  run

First of all extract the xmmword_4022B0 memory to find the AES key,

gef➤  x/16xb 0x4022B0
0x4022b0:       0x2b    0x7e    0x15    0x16    0x28    0xae    0xd2    0xa6
0x4022b8:       0xab    0xf7    0x15    0x88    0x09    0xcf    0x4f    0x3c

Next, inspect the encrypted (0x404040) memory to find the encrypted data.

gef➤  x/64xb &encrypted
0x404040 <encrypted>:   0x0f    0x5f    0xa3    0xb9    0x09    0x23    0x71    0x50
0x404048 <encrypted+8>: 0xbb    0x4f    0x6f    0x6b    0x88    0x1d    0x96    0xc2
0x404050 <encrypted+16>:        0x80    0x29    0x5f    0xe0    0x71    0x90    0x64    0xa6
0x404058 <encrypted+24>:        0xe5    0x35    0x86    0x64    0xb4    0x0c    0xcb    0xb4
0x404060 <encrypted+32>:        0xd8    0x23    0x6f    0x12    0x02    0x54    0xe2    0x0b
0x404068 <encrypted+40>:        0x94    0x83    0xde    0x09    0xf4    0x3e    0x6d    0x24
0x404070 <completed.0>: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x404078:       0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00

All values ​​have been successfully obtained, here are the final solver

solver.py
 1from Crypto.Cipher import AES
 2
 3key = bytes([0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c])
 4enc = bytes([0x0f, 0x5f, 0xa3, 0xb9, 0x09, 0x23, 0x71, 0x50, 0xbb, 0x4f, 0x6f, 0x6b, 0x88, 0x1d, 0x96, 0xc2, 0x80, 0x29, 0x5f, 0xe0, 0x71, 0x90, 0x64, 0xa6, 0xe5, 0x35, 0x86, 0x64, 0xb4, 0x0c, 0xcb, 0xb4, 0xd8, 0x23, 0x6f, 0x12, 0x02, 0x54, 0xe2, 0x0b, 0x94, 0x83, 0xde, 0x09, 0xf4, 0x3e, 0x6d, 0x24])
 5
 6# Initialize AES cipher in ECB mode with the key
 7cipher = AES.new(key, AES.MODE_ECB)
 8
 9dec = cipher.decrypt(enc)
10print(dec.strip())
$ python3 solver.py
b'flag{d41d8cd98f00b204e9800998ecf8427e}'
FLAG

flag{d41d8cd98f00b204e9800998ecf8427e}


Taylor’s First Swift

Overview

Author: @alden

Description: OMG did you hear that they named a programming language after Taylor Swift?

taylor

Second rev category challenge, based on the challenge description it looks like it uses the Swift or maybe Objective-C ? ok, whatever..

The xorEncrypt function takes two arguments, both of which are arrays of bytes. It enumerates over the first array, and for each element, it performs an XOR operation with an element from the second array at a corresponding index. The index is calculated as the current index modulo the length of the second array. The result of the XOR operation is then base64 encoded and returned.

decompiled xorEncrypt
__int64 __fastcall xorEncrypt(_:_:)(__int64 a1, __int64 a2)
{
  __int64 v2; // x0
  __int64 result; // x0
  __int64 v4; // x21
  __int64 v5; // x1
  __int64 v6; // x0
  __int64 v7; // [xsp+8h] [xbp-C8h]
  __int64 v8; // [xsp+10h] [xbp-C0h]
  __int64 v9; // [xsp+20h] [xbp-B0h]
  __int64 v10; // [xsp+30h] [xbp-A0h]
  __int64 v11; // [xsp+38h] [xbp-98h]
  __int64 v13; // [xsp+50h] [xbp-80h]
  __int64 v14; // [xsp+58h] [xbp-78h]
  char v15[16]; // [xsp+60h] [xbp-70h] BYREF
  __int64 v16; // [xsp+70h] [xbp-60h]
  __int64 v17; // [xsp+78h] [xbp-58h] BYREF
  __int64 v18; // [xsp+80h] [xbp-50h]
  __int64 v19[5]; // [xsp+88h] [xbp-48h] BYREF

  v18 = 0LL;
  v19[4] = a1;
  v19[3] = a2;
  v19[1] = a1;
  v10 = __swift_instantiateConcreteTypeFromMangledName(&_sSays5UInt8VGMD);
  v11 = lazy protocol witness table accessor for type [UInt8] and conformance [A]();
  Sequence.enumerated()(v10);
  v19[0] = v19[2];
  swift_bridgeObjectRetain(a2);
  v16 = a2;
  v13 = __swift_instantiateConcreteTypeFromMangledName(&_ss18EnumeratedSequenceVySays5UInt8VGGMD);
  v2 = lazy protocol witness table accessor for type EnumeratedSequence<[UInt8]> and conformance EnumeratedSequence<A>();
  result = Sequence.map<A>(_:)(partial apply for closure #1 in xorEncrypt(_:_:), v15, v13, &dword_100000014, v2);
  v14 = result;
  if ( v4 )
  {
    __break(1u);
  }
  else
  {
    swift_bridgeObjectRelease(a2);
    outlined destroy of EnumeratedSequence<[UInt8]>(v19);
    v18 = v14;
    swift_bridgeObjectRetain(v14);
    v17 = v14;
    v7 = Data.init<A>(_:)(&v17, v10, v11);
    v8 = v5;
    v6 = default argument 0 of Data.base64EncodedString(options:)();
    v9 = Data.base64EncodedString(options:)(v6, v7, v8);
    outlined consume of Data._Representation(v7, v8);
    swift_bridgeObjectRelease(v14);
    return v9;
  }
  return result;
}

The flagCheck function takes the user’s input and compares it to a hardcoded string. It first converts the input and the hardcoded string to arrays of bytes. Then it calls the xorEncrypt function with these two arrays as arguments. The result is compared to another hardcoded base64 string.

decompiled flagCheck
__int64 __fastcall flagCheck(_:)(__int64 a1, __int64 a2)
{
  __int64 v2; // x1
  __int64 v3; // x1
  __int64 v4; // x0
  _BYTE *v5; // x1
  __int64 v6; // x1
  __int64 v7; // x1
  __int64 v8; // x0
  void *v9; // x1
  __int64 v13; // [xsp+18h] [xbp-B8h]
  __int64 v14; // [xsp+20h] [xbp-B0h]
  __int64 v15; // [xsp+28h] [xbp-A8h]
  __int64 v16; // [xsp+50h] [xbp-80h]
  __int64 v17; // [xsp+58h] [xbp-78h]
  __int64 v18; // [xsp+60h] [xbp-70h]
  __int64 v19; // [xsp+68h] [xbp-68h]
  char v20; // [xsp+74h] [xbp-5Ch]
  __int64 v21[4]; // [xsp+78h] [xbp-58h] BYREF
  __int64 v22[3]; // [xsp+98h] [xbp-38h] BYREF
  __int64 v23[4]; // [xsp+B0h] [xbp-20h] BYREF

  v23[2] = a1;
  v23[3] = a2;
  v23[0] = ((__int64 (*)(void))String.utf8.getter)();
  v23[1] = v2;
  lazy protocol witness table accessor for type String.UTF8View and conformance String.UTF8View();
  v19 = Array.init<A>(_:)(v23, &dword_100000014, &dword_100000004);
  v22[2] = v19;
  v22[0] = String.utf8.getter(a1, a2);
  v22[1] = v3;
  v14 = Array.init<A>(_:)(v22, &dword_100000014, &dword_100000004);
  v4 = _allocateUninitializedArray<A>(_:)(9LL, &dword_100000014);
  *v5 = 115;
  v5[1] = 119;
  v5[2] = 105;
  v5[3] = 102;
  v5[4] = 116;
  v5[5] = 105;
  v5[6] = 101;
  v5[7] = 115;
  v5[8] = 33;
  v13 = _finalizeUninitializedArray<A>(_:)(v4, &dword_100000014);
  v15 = xorEncrypt(_:_:)(v14, v13);
  v18 = v6;
  swift_bridgeObjectRelease(v13);
  swift_bridgeObjectRelease(v14);
  v21[2] = v15;
  v21[3] = v18;
  v21[0] = String.utf8.getter(v15, v18);
  v21[1] = v7;
  v17 = Array.init<A>(_:)(v21, &dword_100000014, &dword_100000004);
  v8 = _allocateUninitializedArray<A>(_:)(52LL, &dword_100000014);
  qmemcpy(v9, "FRsIAQ8PVBUVEREIVERbBkURFkUIBxVQVkAYFxJfV0FYVkIVQgo=", 52);
  v16 = _finalizeUninitializedArray<A>(_:)(v8, &dword_100000014);
  v20 = static Array<A>.== infix(_:_:)(v17);
  swift_bridgeObjectRelease(v16);
  swift_bridgeObjectRelease(v17);
  swift_bridgeObjectRelease(v18);
  swift_bridgeObjectRelease(v19);
  return v20 & 1;

Final solver

solver.py
 1import base64
 2
 3# Hardcoded string in ASCII values
 4hardcoded_string = [115, 119, 105, 102, 116, 105, 101, 115, 33]  # "swifties!"
 5
 6hardcoded_base64 = "FRsIAQ8PVBUVEREIVERbBkURFkUIBxVQVkAYFxJfV0FYVkIVQgo="
 7decoded_base64 = base64.b64decode(hardcoded_base64)
 8
 9result = ''.join(chr(decoded_base64[i] ^ hardcoded_string[i % len(hardcoded_string)]) for i in range(len(decoded_base64)))
10print(result)
$ python3 solver.py
flag{f1f4bfa202c60e2aaa9339de61513141}
FLAG

flag{f1f4bfa202c60e2aaa9339de61513141}


Ring Cycle 1 - Basics

Overview

Author: @awesome10billion

Description: Let us start with a simple one and see if you can break into this vault.

NOTE, the plaintext that you provide the binary should be readable English. Reverse engineer the binary to understand what it really does… patching the binary will ultimately give you the wrong answer.

basics.txt
basics

Next, the first of rev category challenge group and the last rev category I solved.

let’s decompile first

decompiled main
int __cdecl main(int argc, const char **argv, const char **envp)
{
  size_t v4; // rax
  int i; // [rsp+4h] [rbp-6Ch]
  FILE *stream; // [rsp+8h] [rbp-68h]
  __int64 nmemb; // [rsp+10h] [rbp-60h]
  void *ptr; // [rsp+18h] [rbp-58h]
  char v9[16]; // [rsp+20h] [rbp-50h] BYREF
  char s[56]; // [rsp+30h] [rbp-40h] BYREF
  unsigned __int64 v11; // [rsp+68h] [rbp-8h]

  v11 = __readfsqword(0x28u);
  init(argc, argv, envp);
  printf("What is the passphrase of the vault?\n> ");
  fgets(s, 50, stdin);
  if ( (unsigned __int8)check(s) )
  {
    stream = fopen("basics.txt", "r");
    if ( !stream )
      return -1;
    fseek(stream, 0LL, 2);
    nmemb = ftell(stream);
    fseek(stream, 0LL, 0);
    ptr = calloc(nmemb, 1uLL);
    if ( !ptr )
      return -1;
    fread(ptr, 1uLL, nmemb, stream);
    fclose(stream);
    printf((const char *)ptr);
    v4 = strlen(s);
    MD5(s, v4, v9);
    printf("flag{");
    for ( i = 0; i <= 15; ++i )
      printf("%02x", (unsigned __int8)v9[i]);
    puts("}");
  }
  else
  {
    puts("Wrong passphrase!");
  }
  return 0;
}
decompiled check
_BOOL8 __fastcall check(__int64 a1)
{
  char v2; // [rsp+11h] [rbp-8Fh]
  char v3; // [rsp+12h] [rbp-8Eh]
  char v4; // [rsp+13h] [rbp-8Dh]
  int i; // [rsp+14h] [rbp-8Ch]
  int j; // [rsp+18h] [rbp-88h]
  int k; // [rsp+1Ch] [rbp-84h]
  char s1[64]; // [rsp+20h] [rbp-80h] BYREF
  char s2[56]; // [rsp+60h] [rbp-40h] BYREF
  unsigned __int64 v10; // [rsp+98h] [rbp-8h]

  v10 = __readfsqword(0x28u);
  for ( i = 0; i <= 24; ++i )
  {
    v4 = *(_BYTE *)(i + a1);
    s1[i] = *(_BYTE *)(50 - i - 1LL + a1);
    s1[49 - i] = v4;
  }
  for ( j = 0; j <= 49; ++j )
  {
    v3 = s1[j];
    s1[j] = s1[j + 1];
    s1[j + 1] = v3;
  }
  s1[49] = 0;
  for ( k = 0; k <= 47; k += 2 )
  {
    v2 = s1[k];
    s1[k] = s1[k + 1];
    s1[k + 1] = v2;
  }
  strcpy(s2, "eyrnou jngkiaccre af suryot arsto  tdyea rre aouY");
  return strcmp(s1, s2) == 0;
}

The check function is used to validate the passphrase input. If the passphrase is correct, the main function will print out the flag.

The check function does the following operations on the input string:

  1. Swaps every pair of characters.
  2. Shifts all characters one position to the right.
  3. Reverses the string.

Final solver,

solver.py
 1import subprocess
 2
 3def reverse_check(s):
 4    s = list(s)
 5    for k in range(0, 47, 2):
 6        s[k], s[k + 1] = s[k + 1], s[k]
 7    s = ''.join(s)
 8    s = s[1:] + s[0]
 9    s = s[::-1]
10    s = s[1:] + s[0]
11    return s
12
13passphrase = reverse_check("eyrnou jngkiaccre af suryot arsto  tdyea rre aouY")
14print(passphrase)
15
16subprocess.run(['./basics'], input=passphrase, text=True)
$ python3 solver.py
You are ready to start your safe cracking journey
What is the passphrase of the vault?
> *******************************************************************************
          |                   |                  |                     |
 _________|________________.=""_;=.______________|_____________________|_______
|                   |  ,-"_,=""     `"=.|                  |
|___________________|__"=._o`"-._        `"=.______________|___________________
          |                `"=._o`"=._      _`"=._                     |
 _________|_____________________:=._o "=._."_.-="'"=.__________________|_______
|                   |    __.--" , ; `"=._o." ,-"""-._ ".   |
|___________________|_._"  ,. .` ` `` ,  `"-._"-._   ". '__|___________________
          |           |o`"=._` , "` `; .". ,  "-._"-._; ;              |
 _________|___________| ;`-.o`"=._; ." ` '`."\` . "-._ /_______________|_______
|                   | |o;    `"-.o`"=._``  '` " ,__.--o;   |
|___________________|_| ;     (#) `-.o `"=.`_.--"_o.-; ;___|___________________
____/______/______/___|o;._    "      `".o|o_.--"    ;o;____/______/______/____
/______/______/______/_"=._o--._        ; | ;        ; ;/______/______/______/_
____/______/______/______/__"=._o--._   ;o|o;     _._;o;____/______/______/____
/______/______/______/______/____"=._o._; | ;_.--"o.--"_/______/______/______/_
____/______/______/______/______/_____"=.o|o_.--""___/______/______/______/____
/______/______/______/______/______/______/______/______/______/______/[TomekK]
*******************************************************************************
flag{8562e979f1f754537a4e872cc20a73e8}
FLAG

flag{8562e979f1f754537a4e872cc20a73e8}



Web Exploitation

iDoor

Overview

Author: @awesome10billion

Description: It’s Apple’s latest innovation, the “iDoor!” … well, it is basically the Ring Doorbell camera, but the iDoor offers a web-based browser to monitor your camera, and super secure using ultimate cryptography with even SHA256 hashing algorithms to protect customers! Don’t even think about snooping on other people’s cameras!!

http://challenge.nahamcon.com:31646

The service are already down, so here’s my solver

solver.py
 1import re
 2import hashlib
 3import requests
 4from bs4 import BeautifulSoup
 5
 6base_url = "http://challenge.nahamcon.com:31646/"
 7
 8for i in range(20):
 9    sha256_hash = hashlib.sha256(str(i).encode()).hexdigest()
10    url = base_url + sha256_hash
11    response = requests.get(url)
12    soup = BeautifulSoup(response.text, 'html.parser')
13    card_body = soup.find('div', class_='card-body')
14    print(f"URL: {url}")
15    print(f"Response:\n{card_body}\n")
16
17    flag = re.findall(r'flag{.*}', str(card_body))
18
19    if flag:
20        print(flag[0])
21        break

The script bruteforces a specific URL by replacing parts of the URL with a SHA256 hash of numbers 0 to 19. For each generated URL, the script performs an HTTP GET request and looks for the flag{.*} pattern in the HTML response.

If the pattern is found (which means the flag has been found), the script prints the flag and stops. Otherwise, the script will continue to the next iteration until all numbers in the range have been tried. In this case, the script encountered the flag on the first iteration (number 0), so the script stopped after the first iteration.

$ python3 solver.py
URL: http://challenge.nahamcon.com:31646/5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9
Response:
<div class="card-body">
<div style="text-align: center; vertical-align: middle; height: 100%;">
<h1><b><code>flag{770a058a80a9bca0a87c3e2ebe1ee9b2}</code></b></h1>
</div>
</div>
flag{770a058a80a9bca0a87c3e2ebe1ee9b2}
FLAG

flag{770a058a80a9bca0a87c3e2ebe1ee9b2}


All About Robots

Overview

Author: @JohnHammond

Description: Oh wow! Now you can learn all about robots, with our latest web service, All About Robots!!

http://challenge.nahamcon.com:30998/

The service are already down, so here’s my solver

solver.py
 1import re
 2import hashlib
 3import requests
 4from bs4 import BeautifulSoup
 5
 6base_url = "http://challenge.nahamcon.com:30998"
 7
 8robotstxt = "/robots.txt"
 9endpoint = "/open_the_pod_bay_doors_hal_and_give_me_the_flag.html"
10
11response = requests.get(base_url + endpoint)
12
13flag = re.findall(r'flag{.*}', response.text)
14
15print(flag[0])
FLAG

flag{3f19b983c1de42bd49af1a237d7e57b9}



Warmups

QRRRRRRRR

Overview

Author: @JohnHammond

Description: Wait a second, they made QR codes longer!?!

qrrrrrrrr.png

They provide a long qr image



But the athor which John Hammond has discussed this in his YouTube video,



We can scan it using QRQR - QR Code® Reader



FLAG

flag{a44557e380e3baae9c21c738664c6142}


EICAR

Overview

Author: @JohnHammond

Description: What is the MD5 hash of this file?

Wrap the hexadecimal value in the flag{ prefix and { suffix to match the standard flag format.

Note, your antivirus engine might flag this file – don’t worry, I promise it’s not malware :)

eicar

Here we go, just wrap it to flag{.*}

$ md5sum eicar
44d88612fea8a8f36de82e1278abb02f  eicar
FLAG

flag{44d88612fea8a8f36de82e1278abb02f}


That’s Not My Base

Overview

Author: @JohnHammond

Description: Everyone knows about Base64, but do you know about this one?

(Remember, the flag format starts with flag{!)

F#S<YRXdP0Fd=,%J4c$Ph7XV(gF/*]%C4B<qlH+%3xGHo)\

Just decode from base92 F#S<YRXdP0Fd=,%J4c$Ph7XV(gF/*]%C4B<qlH+%3xGHo)\

Decoded from base92

FLAG

flag{784454a9509196a33dba242c423c057a}


Uriel

Overview

Author: @JohnHammond

Description: Everyone knows about Base64, but do you know about this one?

(Remember, the flag format starts with flag{!)

F#S<YRXdP0Fd=,%J4c$Ph7XV(gF/*]%C4B<qlH+%3xGHo)\

Just decode from url decode 2 times

solver.py
1from urllib.parse import unquote
2
3url = "%25%36%36%25%36%63%25%36%31%25%36%37%25%37%62%25%33%38%25%36%35%25%36%36%25%36%35%25%36%32%25%33%36%25%33%36%25%36%31%25%33%37%25%33%31%25%33%39%25%36%32%25%33%37%25%33%35%25%36%31%25%33%34%25%36%32%25%33%37%25%36%33%25%33%36%25%33%33%25%33%34%25%36%34%25%33%38%25%33%38%25%33%35%25%33%37%25%33%38%25%33%38%25%36%34%25%36%36%25%36%33%25%37%64"
4
5# Perform unquoting twice
6print(unquote(unquote(url)))
FLAG

flag{8efeb66a719b75a4b7c634d885788dfc}


Twine

Overview

Author: @JohnHammond#6971

Description: Google tells me that twine means: “strong thread or string consisting of two or more strands of hemp, cotton, or nylon twisted together.”

twine.jpg

Just strings it

$ strings twine.jpg | grep -o 'flag{[^}]*}'
flag{4ac54e3ba5f8f09049f3ad62403abb25}
FLAG

flag{4ac54e3ba5f8f09049f3ad62403abb25}