CAT CTF 26 Finals

62 minute read

الْحَمْدُ لِلّهِ الَّذِي هَدَانَا لِهَـذَا وَمَا كُنَّا لِنَهْتَدِيَ لَوْلا أَنْ هَدَانَا اللّهُ

Date: 25/09/2026

By: Y0un15


Little Talk

I participated in CAT CTF 2026 with team BR0K3N SH311 and we qualified for the finals, and I am happy to share that we took first place img

These are some walkthroughs for the DFIR and rev challenges

I didn’t manage to solve them all during the CTF, but I had to look into them again after the CTF

so let’s start

67 iq

img

this was the easy rev challenge and it was the only solved rev challenge during the CTF

with a quick file check and running the binary, it was an ELF binary Sudoku game

game

so I opened it in IDA to see the logic and how to crack it

After decompiling main and checking the logic

game game

I found that we can just avoid the game logic and the checks and let the binary print the flag and do the work for us

This is possible because the code checks the input, and if it was incorrect it prints incorrect and exits and the decryption of the flag will not happenes

So, I just had to jump over all these checks and let the binary decode and print the flag

To do so, we need to stop after the initialization of the v18 variable and then jump to the decoding for loop

game

So I made a breakpoint at v14 = v18; and before the exit after printing Incorrect so the initialization of the key finishes

I started the remote Linux debugger

game after adding a random 81-digit input 124367589586249317793158264312594678857621493469783152945836721278415936631972845 letting the do-while loop run so the key gets initialized game

then I grabbed the offset to jump to

game or I could just hit Ctrl+N to set the RIP value to that address

Then i ran the game and it decoded the flag and printed it without having to staticlly doing it or brute force anything game CATF{QU1CK_BR41N_W4RM_UP}

Protege

k1r17oo I made some progress on this challenge during the CTF but couldn’t finish it in time

In this challenge we were given 3 files: a DLL, an EXE file, and a PCAP file

So let’s do a quick analysis of the given files

Protege.exe

After loading it in IDA and giving it a look, I found that Protege.exe is mostly a launcher or wrapper for the game, and the logic is inside the DLL it loads

k1r17oo Function sub_140001BF0 probably handles the key input for the game, then it calls sub_140001810() after the window is created

k1r17oo

And that function is responsible for loading the DLL, it uses GetProcAddress to resolve a number of exported functions:

Game_ApiVersion
Game_Create
Game_Initialize
Game_Destroy
Game_Update
Game_Render
Game_OnKey
Game_ShouldQuit

k1r17oo when the program exits, it calls Game_Destroy, unloads the DLL with FreeLibrary, and starts the cleanup process

I tried to run this binary and debug it, but I was hit with this error

k1r17oo

We will see why this happened and how to avoid it after we analyze the DLL

Protege.dll

From the EXE we understood that this DLL gets loaded and some functions get resolved, so we need to understand what they do But first we need to see what the function Game_Initialize does
Because it was the last call in the EXE before unloading the DLL and starting the cleanup process(and also it has the DirectX11 message thing), so let’s analyze it first

k1r17oo

It has two calls: one to sub_180001680 and the other to sub_180002C30

k1r17oo

Analyzing function sub_180001680

k1r17oo

1- It first calls sub_180002980, which calls the APIs GetTickCount64, GetCurrentProcessId, and BCryptGenRandom. From the dynamic analysis I did during the CTF, this function creates some kind of UUID identifier, so I will call it create_UUID

k1r17oo

2- Then calls to sub_180001CB0 and sub_180001A20. During the CTF I found that they have an AES signature and do some important initialization, so let’s break each one down in detail

First the function sub_180001CB0 k1r17oo

It first obtains the current system time with GetSystemTimeAsFileTime and then creates the string <UUID>|<timestamp>|PAYLOADKEY

    GetSystemTimeAsFileTime(&SystemTimeAsFileTime);
    pcbBinary = (*&SystemTimeAsFileTime - 116444736000000000LL) / 0x989680uLL;
    if ( sub_180002C70(Buffer, 0xA0uLL, "%s|%llu|PAYLOADKEY") < 0 )

Then a call to function sub_1800013C0, which takes some interesting arguments

  • 160-character buffer
  • a 32-byte key: EE1CB148F919FE9AC9E13DD62730A15D082A915773D2E7C04AEDA19CE1045233

And inside this function is the AES function call sub_180001000 that I previously found with the FindCrypt plugin during the CTF

 v10 = sub_180001000(1LL, a1, v9, a2, a3, &byte_180004388, 16, &pbBinary, &cbBinary);
  • byte_180004388 (FF969B2B4C3B76EB64362BAE3E044145), which is the IV for the AES encryption
__int64 __fastcall sub_180001000(
        int a1,
        UCHAR *a2,
        ULONG a3,
        UCHAR *a4,
        ULONG cbSecret,
        _OWORD *a6,
        int a7,
        UCHAR **a8,
        ULONG *a9)
{
  UCHAR *v12; // rbx
  HANDLE ProcessHeap; // rax
  UCHAR *v14; // rax
  int v15; // edi
  NTSTATUS v16; // eax
  HANDLE v17; // rax
  UCHAR *v18; // rsi
  NTSTATUS v19; // eax
  HANDLE v20; // rax
  ULONG *v21; // rax
  ULONG v22; // ecx
  HANDLE v24; // rax
  HANDLE v25; // rax
  ULONG cbOutput; // [rsp+50h] [rbp-69h] BYREF
  UCHAR pbOutput[4]; // [rsp+54h] [rbp-65h] BYREF
  BCRYPT_KEY_HANDLE phKey; // [rsp+58h] [rbp-61h] BYREF
  BCRYPT_ALG_HANDLE phAlgorithm; // [rsp+60h] [rbp-59h] BYREF
  ULONG pcbResult; // [rsp+68h] [rbp-51h] BYREF
  UCHAR v31[4]; // [rsp+6Ch] [rbp-4Dh] BYREF
  int v32; // [rsp+70h] [rbp-49h]
  ULONG *v33; // [rsp+78h] [rbp-41h]
  UCHAR pbIV[16]; // [rsp+80h] [rbp-39h] BYREF
  UCHAR pbInput[16]; // [rsp+90h] [rbp-29h] BYREF
  __int128 v36; // [rsp+A0h] [rbp-19h]

  v32 = a1;
  v33 = a9;
  v12 = 0LL;
  phAlgorithm = 0LL;
  phKey = 0LL;
  *pbOutput = 0;
  pcbResult = 0;
  *v31 = 0;
  cbOutput = 0;
  *pbInput = xmmword_180004480;
  v36 = xmmword_180004490;
  if ( !a2 || !a3 || !a4 || !a6 || a7 != 16 || !a8 || !a9 || ((cbSecret - 16) & 0xFFFFFFE7) != 0 || cbSecret == 40 )
    return 0LL;
  *a8 = 0LL;
  *a9 = 0;
  if ( BCryptOpenAlgorithmProvider(&phAlgorithm, L"AES", 0LL, 0) < 0 )
    goto LABEL_35;
  if ( BCryptSetProperty(phAlgorithm, L"ChainingMode", pbInput, 0x20u, 0) < 0 )
    goto LABEL_35;
  if ( BCryptGetProperty(phAlgorithm, L"ObjectLength", pbOutput, 4u, &pcbResult, 0) < 0 )
    goto LABEL_35;
  if ( !*pbOutput )
    goto LABEL_35;
  if ( BCryptGetProperty(phAlgorithm, L"BlockLength", v31, 4u, &pcbResult, 0) < 0 )
    goto LABEL_35;
  if ( *v31 != 16 )
    goto LABEL_35;
  ProcessHeap = GetProcessHeap();
  v14 = HeapAlloc(ProcessHeap, 0, *pbOutput);
  v12 = v14;
  if ( !v14 || BCryptGenerateSymmetricKey(phAlgorithm, &phKey, v14, *pbOutput, a4, cbSecret, 0) < 0 )
    goto LABEL_35;
  v15 = v32;
  *pbIV = *a6;
  v16 = v32
      ? BCryptEncrypt(phKey, a2, a3, 0LL, pbIV, 0x10u, 0LL, 0, &cbOutput, 1u)
      : BCryptDecrypt(phKey, a2, a3, 0LL, pbIV, 0x10u, 0LL, 0, &cbOutput, 1u);
  if ( v16 < 0 || !cbOutput || (v17 = GetProcessHeap(), (v18 = HeapAlloc(v17, 0, cbOutput)) == 0LL) )
  {
LABEL_35:
    if ( phKey )
      BCryptDestroyKey(phKey);
    if ( phAlgorithm )
      BCryptCloseAlgorithmProvider(phAlgorithm, 0);
    if ( v12 )
    {
      if ( *pbOutput )
        memset(v12, 0, *pbOutput);
      v25 = GetProcessHeap();
      HeapFree(v25, 0, v12);
    }
    return 0LL;
  }
  *pbIV = *a6;
  if ( v15 )
    v19 = BCryptEncrypt(phKey, a2, a3, 0LL, pbIV, 0x10u, v18, cbOutput, &cbOutput, 1u);
  else
    v19 = BCryptDecrypt(phKey, a2, a3, 0LL, pbIV, 0x10u, v18, cbOutput, &cbOutput, 1u);
  if ( v19 < 0 || !cbOutput )
  {
    if ( cbOutput )
      memset(v18, 0, cbOutput);
    v24 = GetProcessHeap();
    HeapFree(v24, 0, v18);
    goto LABEL_35;
  }
  BCryptDestroyKey(phKey);
  BCryptCloseAlgorithmProvider(phAlgorithm, 0);
  if ( *pbOutput )
    memset(v12, 0, *pbOutput);
  v20 = GetProcessHeap();
  HeapFree(v20, 0, v12);
  v21 = v33;
  v22 = cbOutput;
  *a8 = v18;
  *v21 = v22;
  return 1LL;
}

this is AES encryption in CBC mode using the key and IV mentioned above

And after that, the encrypted data is base64 encoded and wrapped into this JSON:

{"data":"<base64 ciphertext>"}

So function sub_1800013C0 is responsible for the AES encryption, base64 encoding, and wrapping that data k1r17oo Then function sub_180002600 sends the request to api.protege.test:8080 with a POST request to /api/protege/init, as we will see in the PCAP file

Then function sub_180001870 extracts this base64 string from the JSON field, saves it to pszString, and then uses CryptStringToBinaryA to decode that base64 k1r17oo

Then another call to sub_180001000, which likely decrypts the AES-encrypted response

So basically function sub_180001CB0 does this:

  • Generates a UUID and formats it as UUID | timestamp | PAYLOADKEY
  • Then function sub_1800013C0 encrypts the buffer using AES-CBC with the embedded key and IV
  • Base64 encodes and wraps that data
  • Sends a POST request to api.protege.test:8080/api/protege/init
  • Receives the server response, Base64 decodes it, and decrypts it using AES-CBC

Then function sub_180001A20 locates a resource inside that DLL and AES decrypts it using the server response session key and IV. To get those, we need to inspect and decode the provided PCAP file, but for now let’s skip ahead and analyze the rest of it

k1r17oo After decrypting the resource with the server key and IV, it checks if the header is a valid PE or not, and if it is, it then dumps it inside the temp folder under the filename ProtegePayload_<tickcount>_<pid>.exe

And if the writing fails, it wipes the decrypted EXE from memory and closes any open handles

{
        FileW = CreateFileW(Buffer, 0x40000000u, 0, 0LL, 2u, 0x2100u, 0LL);
        if ( FileW == -1LL )
        {
          *Buffer = 0;
        }
        else
        {
          v20 = v17;
          v21 = v16;
          if ( v16 )
          {
            while ( 1 )
            {
              nNumberOfBytesToWrite = 0;
              if ( !WriteFile(FileW, v20, v21, &nNumberOfBytesToWrite, 0LL) || !nNumberOfBytesToWrite )
                break;
              v20 += nNumberOfBytesToWrite;
              v21 -= nNumberOfBytesToWrite;
              if ( !v21 )
                goto LABEL_22;
            }
            CloseHandle(FileW);
            DeleteFileW(Buffer);
            *Buffer = 0;
          }
          else
          {
LABEL_22:
            FlushFileBuffers(FileW);
            CloseHandle(FileW);
            v4 = 1;
          }
        }
      }
    }
  }
  if ( v17 )
  {
    if ( v16 )
      memset(v17, 0, v16);
    ProcessHeap = GetProcessHeap();
    HeapFree(ProcessHeap, 0, v17);
  }
  return v4;
}

So let’s inspect the PCAP and try to find and decode the server IV and key response

Protege.pcap

The PCAP has five streams as shown: k1r17oo k1r17oo k1r17oo k1r17oo k1r17oo

Knowing the key,IV, and the AES mode, we can decrypt stream 0 and get the server response key and IV

I used CyberChef to do this, and here is the recipe

k1r17oo k1r17oo Now we have the key and IV to decrypt the embedded PE from the resources

Decrypting the PE from resources

we can use this recipe

key:9VLcUNZ2DakyH8DlHJnhaXSTqKnBqkjU4q5GXM05NJQ=
iv:aQihqtqOPybZEhsPPMTsIA==

I dumped the resource from Protege.exe file with PE-bear k1r17oo Before using it we need to skip some bytes, so the easier way is to use Resource Hacker and save the RCDATA 101 to disk k1r17oo

After loading the .bin file into the recipe, we get the PE file we need, which is Stage 2 k1r17oo

So i saved it and named it Stage2.exe

Analyzing Stage 2

Loading the PE inside IDA Pro, I immediately noticed the same pattern, where:

  1. The key for the next stream in the PCAP
  2. The IV
  3. Likely the game logic or what caused the next streams

k1r17oo I extracted the key and the IV and went to decrypt the stream 1 data (I noticed that I could decrypt streams 1, 2, and 3 since they have the same UUID and different responses from the server but different arguments READY 1, READY 2, and READY 3) k1r17oo k1r17oo

Now we have this pattern:

{"nonce":"HtYlBM39yePbyDy5","key":"MOQXaM8/HD0fdcrXGwTvv0aqF8tziN5cqrtsiBFZDAU=","opcode":17,"encryptedvalue":"/eL5wRsawt/rWccc"}

so I needed to understand what READY 1, 2, or 3 does and what the game logic does:

Stage 2 connects to the server and sends:

3e1ac478-09d3-45a7-bdd4-a08c77190761|1790217336|READY|1

and it receives and decrypts the server response:

{"nonce":"HtYlBM39yePbyDy5","key":"MOQXaM8/HD0fdcrXGwTvv0aqF8tziN5cqrtsiBFZDAU=","opcode":17,"encryptedvalue":"/eL5wRsawt/rWccc"}

then it calls the function sub_1400021C0, which seems to have the main game logic This function has a lot of math, and the function calls inside it also involve heavy math. It would be very painful to go through each one of them and understand what they do, so I had to guess in general what the flow was

The function sub_140001000 gets called before the string {\"data\":\"", so it’s likely an encryption or decryption algorithm (or both, like the logic in the previous stage) maybe AES?

then function sub_140001FA0 (honestly, I couldn’t figure out what it does at first) so I asked an LLM and it was resolving these APIs:

a1 + 160  → WinHttpOpen
a1 + 168  → WinHttpConnect
a1 + 176  → WinHttpOpenRequest
a1 + 184  → WinHttpSendRequest
a1 + 192  → WinHttpReceiveResponse
a1 + 200  → WinHttpQueryDataAvailable
a1 + 208  → WinHttpReadData
a1 + 216  → WinHttpCloseHandle

so we can name it HTTP_POST_and_json then function sub_140001CD0 and the call inside it to sub_140001550 (I assumed it was the other encryption algorithm used before in the server response we decrypted)

As we saw from the server response, there was a nonce, key, opcode, and encrypted value

So it’s most likely ChaCha20 or Salsa20 (I searched which algorithms use the intrinsics _mm_xor_ps and _mm_loadu_si128 along with a nonce and key)

It would be very painful for me to try solving the state machine and decrypting the flag statically, so I tried to go for the dynamic solution

Solving Dynamically

Why is this possible? Because when sub_140001FA0 tries to send the POST request it fails, but maybe I can add the server response from the PCAP directly in memory and force it to continue instead of terminating

After rebasing IDA to match x64dbg so it would be easier for me to see and jump wherever I want: k1r17oo

I set some breakpoints and did some renaming, then started the binary k1r17oo I also changed the argument to match the one sent in the PCAP: --uuid=3e1ac478-09d3-45a7-bdd4-a08c77190761 from file we will chose command line and we can change it there k1r17oo then i hit F9 until i hit the breakpoint at game_logic

then i stepped into it and continued until i hit the HTTP_POST function while running FakeNet

when i hit the breakpoint, i grabbed the R8 pointer and followed it in the dump, and this is what i got: k1r17oo

now we just need to edit the memory and put the correct server data:

{"data":"p/uQIcjC8ogDrT69LVNaTLkRqz90/A/YFWCHpRiaUEMkcZ5y8+p0JE8br99BJtdSXxCN3sP5Nk8EVKRDVn+8oiKBYdvspbTgi5ZRNO5MAoGE9Y0Fi+3HDbHJhqt3ktsit1qOmlaLYPe2xrgY8yaSgLLaYEfdJvZdAHbvP4Auhl4himw5WxNvRirBQNABbRK9"} 

k1r17oo

after that I continued until it hit the breakpoint again, and I added the next state:

{"data":"nqyfVZesbqDevb9ks1ZypO6RUUEin9vLN+rFNYzxBWY+5UH+Lq/L5T4Qizq5nkpBR8W0o9CX5EH3eSw2lmeZAg/dU5BPm+5Qdpl8qroWfytDCr2dJPaXhDndZOHWJt3/LwAxRs0j2stFD3XBUTy6FlIcCEixj+EPQomiv80jyQHBb3SCVJ1RlQlDAf5FpA96kLxnzGivBvap83I8gp1xFw=="} 

make sure to add 00 (the null terminator) at the end of the string you put

I kept continuing until I hit the HTTP breakpoint again, but there was no POST request after it, so I just kept going until it hit again and I saw the FakeNet response in memory k1r17oo

then I patched it and kept continuing k1r17oo until it hit again

k1r17oo now for the third one:

{"data":"DaLkiXZejLYEZHcvWj6v8fwI3XDTWxwnxQgT0sbcQXFJKpJY+yla/nTaN+uevoaXlBOtk90zyOxaq6JFnnvWoU50BT0wySi7LNVCp8Zj08DZMzj5391IzFien79zsMNU8ML2ZlH/TgvTtprEJtRsnPr2C2wWe0ncVu4fvydHm0XuC4ROgIN8U1NvQFWQvj5t6VmdhOZxUsMpAELra3o2yg=="} 

now we have reached the last one, READY 4: k1r17oo After I patched it:

{"data":"hTSgVq8jpWTqa7OhDiCVGuKw8O2EwupXURHsvJUMAB8HC3t12DMHFNeQDjB7vaP5otXJwpjX1G51fWcG3NCC/PUP/hj8z1VJ17kKWS2QUrpA6W799JWk44K3947esKLBaCwSV5IuEBf/Ka+cWfgL3C1Sr9AdYddiGOreAfBrtR3r0P1BRkdTfYisaQIvZwpdWMJD/0DZYl6K/ndC7HJocghRvcJbpRHp46PjgTz6pM7pQdMCu2E3bSbd1yuzjaRIhW11GXUdwGvRauyl0LxbEaPp+7vpLK1r8/mtAWG06Ek="}

I needed to see what the decrypted server output was to get the flag so I placed a breakpoint on the function sub_140001CD0,and another one right before the check for CATF (the flag format): k1r17oo k1r17oo

And just like that, we solved the challenge without any need for heavy math or custom Python code k1r17oo CACATF{Wh3n_4_m4St3r_1s_T4k3n_0uT_bY_h1s_Pr0t3g3_a42c534ccaf4fb1377abcd44}

Another solution

Creating a mock server to respond to the binary requests

from http.server import HTTPServer, BaseHTTPRequestHandler
import json

RESPONSES = [
    # READY|1
    '{"data":"p/uQIcjC8ogDrT69LVNaTLkRqz90/A/YFWCHpRiaUEMkcZ5y8+p0JE8br99BJtdSXxCN3sP5Nk8EVKRDVn+8oiKBYdvspbTgi5ZRNO5MAoGE9Y0Fi+3HDbHJhqt3ktsit1qOmlaLYPe2xrgY8yaSgLLaYEfdJvZdAHbvP4Auhl4himw5WxNvRirBQNABbRK9"}',
    # READY|2
    '{"data":"nqyfVZesbqDevb9ks1ZypO6RUUEin9vLN+rFNYzxBWY+5UH+Lq/L5T4Qizq5nkpBR8W0o9CX5EH3eSw2lmeZAg/dU5BPm+5Qdpl8qroWfytDCr2dJPaXhDndZOHWJt3/LwAxRs0j2stFD3XBUTy6FlIcCEixj+EPQomiv80jyQHBb3SCVJ1RlQlDAf5FpA96kLxnzGivBvap83I8gp1xFw=="}',
    # READY|3
    '{"data":"DaLkiXZejLYEZHcvWj6v8fwI3XDTWxwnxQgT0sbcQXFJKpJY+yla/nTaN+uevoaXlBOtk90zyOxaq6JFnnvWoU50BT0wySi7LNVCp8Zj08DZMzj5391IzFien79zsMNU8ML2ZlH/TgvTtprEJtRsnPr2C2wWe0ncVu4fvydHm0XuC4ROgIN8U1NvQFWQvj5t6VmdhOZxUsMpAELra3o2yg=="}',
    # READY|4
    '{"data":"hTSgVq8jpWTqa7OhDiCVGuKw8O2EwupXURHsvJUMAB8HC3t12DMHFNeQDjB7vaP5otXJwpjX1G51fWcG3NCC/PUP/hj8z1VJ17kKWS2QUrpA6W799JWk44K3947esKLBaCwSV5IuEBf/Ka+cWfgL3C1Sr9AdYddiGOreAfBrtR3r0P1BRkdTfYisaQIvZwpdWMJD/0DZYl6K/ndC7HJocghRvcJbpRHp46PjgTz6pM7pQdMCu2E3bSbd1yuzjaRIhW11GXUdwGvRauyl0LxbEaPp+7vpLK1r8/mtAWG06Ek="}',
]

request_count = 0

class ReplayHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        global request_count
        length = int(self.headers.get('Content-Length', 0))
        body = self.rfile.read(length)
        print(f"[*] Request #{request_count}: {body[:80]}...")

        resp = RESPONSES[min(request_count, len(RESPONSES)-1)]
        request_count += 1

        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        self.wfile.write(resp.encode('utf-8'))

print("Fake C2 Server listening on 0.0.0.0:8080...")
HTTPServer(('0.0.0.0', 8080), ReplayHandler).serve_forever()

Make sure to add api.protege.test to your hosts file:

Add-Content -Path "C:\Windows\System32\drivers\etc\hosts" -Value "`n127.0.0.1       api.protege.test"

Then you need to run the Stage 2 file with the argument --uuid=3e1ac478-09d3-45a7-bdd4-a08c77190761:

stage2.exe --uuid=3e1ac478-09d3-45a7-bdd4-a08c77190761

k1r17oo

it was a nice challenge and shoutout to k1r17oo for this awesome challenge

gif like

CrackMe

We are given a 64-bit Windows PE executable named chall.exe. Running the executable prompts us to enter 5 separate serial parts:

Enter serial part 1/5: 

The challenge description provided a crucial hint: “Who cares about SERIAL??!”

This hints that there is a shortcut somewhere we can take that will make things much easier crackme

After loading the PE file into IDA Pro, we locate the main verification routine at sub_1400018FF crackme The program first initializes a 40-byte buffer to hold the flag and then enters a loop 5 times. Each time, it asks the user to enter a serial part , and if there is no input error, it calls the validation function:

     if ( !sub_1400017EC(i, Buffer, Size_4, &Size) )
     {
         puts("Invalid serial.");
         return 1;
     }

After all 5 parts are passed, it hashes the final 40-byte string and compares it against 0x774C42D40DDD0703:

   if ( sub_140001605(v6, v9) == 0x774C42D40DDD0703LL )
       printf("Validation complete. Flag: %.*s\n", v9, v6);

crackme

I found 5 blobs in the .rdata section (I was searching for any encrypted blobs so I could trace back from there) and they have a reference in the function sub_1400017EC. crackme Blob_0 : 0A 5E 4D CE 0D B9 D9 10 A3 D1 D7 99 Blob_1 : 69 70 CA 6A B3 8B 86 F9 C0 FD DE 22 Blob_2 : D6 EF 9E C5 CB 73 BE 83 0F 54 28 EB Blob_3 : EA 56 08 E6 3C A1 C6 EE 43 F5 64 D7 Blob_4 : DC 16 F5 EB EA FC 80 66 4E 4B 48

So let’s take a deep dive into the function sub_1400017EC to understand what those five blobs were

  • sub_1400014AA hashes the input to produce a 4-byte key

  • sub_14000150F decrypts the encrypted blob with the calculated key crackme

The program checks that the first decrypted byte is 0x10 or not and calls sub_140001588 to do some other calculation crackme If this check fails, it proceeds to clean up and exit the function, and continues the loop if it wasn’t the last one.

Then the function sub_14000166B is called, and it has some interesting checks that make me call it the parser.

Because it checks whether the output from XORing with the key derived from the input starts with 0x10, 0x21, 0x30, or 0xFF. For example, let’s assume the output is 0x10 0x03 0x45 0x26 0x48 0x21 0x11 0x30 0xFF.

  • 0x10: Reads length, which in our case is 0x3, then copies length bytes from the stream into a buffer.
  • 0x21: Reads add_value, then adds add_value to every copied output byte (out[j] += add_value).
  • 0x30 :No-operation; skips to the next tag
  • 0xFF :Marks end of bytecode and returns success

so becuase we have some fixed prefix 0x10 we can brute force all the blobs and then parse them as fucntino sub_14000166B would do and we ill have the flag and this is the shortcut the was hinting for it in the description

And this is the brute-force script with the parsing logic:

#!/usr/bin/env python3

BLOBS = [
    bytes.fromhex("0A 5E 4D CE 0D B9 D9 10 A3 D1 D7 99"),
    bytes.fromhex("69 70 CA 6A B3 8B 86 F9 C0 FD DE 22"),
    bytes.fromhex("D6 EF 9E C5 CB 73 BE 83 0F 54 28 EB"),
    bytes.fromhex("EA 56 08 E6 3C A1 C6 EE 43 F5 64 D7"),
    bytes.fromhex("DC 16 F5 EB EA FC 80 66 4E 4B 48"),
]

CRCS = [0x6020, 0xDDF5, 0x4837, 0x5932, 0xCC1A]

def crc16_ccitt(data: bytes) -> int:
    crc = 0xFFFF
    for byte in data:
        crc ^= (byte << 8)
        for _ in range(8):
            if crc & 0x8000:
                crc = ((crc << 1) ^ 0x1021) & 0xFFFF
            else:
                crc = (crc << 1) & 0xFFFF
    return crc


def decrypt(blob: bytes, key: bytes) -> bytes:
    return bytes(b ^ ((key[i % 4] + 13 * i) & 0xFF) for i, b in enumerate(blob))


def parse_tlv(data: bytes):
    pos = 0
    out = bytearray()

    while pos < len(data):
        tag = data[pos]
        pos += 1

        if tag == 0xFF:  # END / SUCCESS
            return bytes(out)

        elif tag == 0x10:  # COPY
            if pos >= len(data): return None
            length = data[pos]
            pos += 1
            if pos + length > len(data): return None
            out = bytearray(data[pos:pos + length])
            pos += length

        elif tag == 0x21:  # ADD
            if pos >= len(data): return None
            add_val = data[pos]
            pos += 1
            for j in range(len(out)):
                out[j] = (out[j] + add_val) & 0xFF

        elif tag == 0x30:  # NOP
            continue

        else:
            return None

    return bytes(out)


def main():
    flag_parts = []

    for i, (blob, expected_crc) in enumerate(zip(BLOBS, CRCS)):
        k0 = blob[0] ^ 0x10
        copy_len = 5 if i == 4 else 6
        k1 = ((blob[1] ^ copy_len) - 13) & 0xFF

        found = False
        for k2 in range(256):
            if found:
                break
            for k3 in range(256):
                key = bytes([k0, k1, k2, k3])
                decrypted = decrypt(blob, key)

                if crc16_ccitt(decrypted) == expected_crc:
                    output = parse_tlv(decrypted)
                    if output:
                        text = output.decode("ascii")
                        print(f"Blob {i} -> {text}")
                        flag_parts.append(text)
                        found = True
                        break

    print()
    print("FINAL FLAG:", "".join(flag_parts))


if __name__ == "__main__":
    main()

Running it will give us the flag:

Blob 0 -> CATF{3
Blob 1 -> oBAD_s
Blob 2 -> 3n6s_h
Blob 3 -> 1s_r3g
Blob 4 -> 4rd5}

FINAL FLAG: CATF{3oBAD_s3n6s_h1s_r3g4rd5}

I was planning to get the keys from the brute force, patch them in memory, and let the binary run with a couple of forced jumps to see the flag, parsing the decrypted pieces directly in Python was much faster and i lost my interist in middel of it lol

CATF{3oBAD_s3n6s_h1s_r3g4rd5}

Vault

vault I was given a PE binary to analyze and find the flag inside the Vault. During the CTF a hint was dropped: You need to know what does software breakpoint actually cost you

It was a huge hint cuz I didn’t know why the exception error was happening So let’s take a look into the code flow and try to find the flag

Trying To Understand Code Flow

Loading the binary in IDA Pro, we end up at the start function: vault So let’s start analyzing the function sub_7FF676F611B0 and the interesting calls inside it

Then I launched the binary to see what happens and potentially trace back from that: vault

Now we have these strings: Welcome to my vault, Enter the password:, nope, so I located them in IDA and cross-referenced them

I ended up with this function sub_7FF676F61975 which I will call prob_main: vault

This is easily crackable, but during the CTF I realized this is a fake path and it gives us this:

y0u_g0t_My_p4ss_but_th1s_1s_n0t_th3_fl4g_y0u_n33d_t0_l00k_3ls3wh3r3

and here is the recipe to do so cyberchef_recipe

So back to the binary and let’s see what else we have

Searching the strings in IDA, I found the string Correct Flag, and tracing back from it leads to function TlsCallback_1

vault

I have potentially found the correct path check

There were some TLS callback functions and they looked promising, so I started to analyze the code flow around them

TlsCallback_0

void __fastcall TlsCallback_0(__int64 a1, int a2)
{
  const WCHAR *CommandLineW; // rax
  DWORD flOldProtect; // [rsp+48h] [rbp-18h] BYREF
  int pNumArgs; // [rsp+4Ch] [rbp-14h] BYREF
  HLOCAL hMem; // [rsp+50h] [rbp-10h]
  int i; // [rsp+5Ch] [rbp-4h]

  if ( a2 == 1 )
  {
    pNumArgs = 0;
    CommandLineW = GetCommandLineW();
    hMem = CommandLineToArgvW(CommandLineW, &pNumArgs);
    if ( pNumArgs > 1 )
    {
      VirtualAlloc(0x20260000, 0x1000uLL, 0x3000u, 4u);
      WideCharToMultiByte(0, 0, *(hMem + 1), -1, 0x202600A0, 111, 0LL, 0LL);
    }
    else
    {
      for ( i = 0; i < strlen(Str); ++i )
        Str[i] ^= 0xAAu;
      OutputDebugStringA("\n\n\n\n\n");
      OutputDebugStringA(Str);
      OutputDebugStringA("\n\n");
      VirtualProtect(&off_7FF676F71040, 0x20uLL, 4u, &flOldProtect);
      off_7FF676F71040 = 0LL;
      off_7FF676F71048 = 0LL;
      off_7FF676F71050 = 0LL;
      off_7FF676F71058 = 0LL;
    }
    if ( hMem )
      LocalFree(hMem);
  }
}

A TLS callback runs before the entry point or main function

Analyzing the TlsCallback_0 function, I found that the program checks if it was run with an argument or not, so there are two paths:

  1. If supplied with an argument: It allocates memory at 0x20260000 and saves the argument to the address 0x202600A0 (base + 0xA0) with a max size of 111 bytes (you can check the manual here)

  2. If no argument was provided: It does an XOR decryption on the variable Str: vault

which gives this message:

k1r17oo thinks the challenge is too hard for you so I gave you this message as hint.

I used this recipe

It prints that message to the debugger’s log, then clears pointers to disable callbacks 1 through 6:

vault

TlsCallback_1

void __fastcall TlsCallback_1(__int64 a1, int a2)
{
  HANDLE CurrentProcess; // rax
  DWORD v3; // [rsp+38h] [rbp-68h] BYREF
  BYTE v4[4]; // [rsp+3Ch] [rbp-64h] BYREF
  BYTE v5[2]; // [rsp+40h] [rbp-60h] BYREF
  __int16 v6; // [rsp+42h] [rbp-5Eh]
  int v7; // [rsp+44h] [rbp-5Ch]
  int v8; // [rsp+48h] [rbp-58h]
  __int64 v9; // [rsp+4Ch] [rbp-54h]
  __int64 v10; // [rsp+54h] [rbp-4Ch]
  DWORD pdwDataLen; // [rsp+5Ch] [rbp-44h] BYREF
  BYTE v12[8]; // [rsp+60h] [rbp-40h] BYREF
  __int64 v13; // [rsp+68h] [rbp-38h]
  HCRYPTKEY hKey; // [rsp+78h] [rbp-28h] BYREF
  HCRYPTHASH phHash; // [rsp+80h] [rbp-20h] BYREF
  HCRYPTPROV phProv; // [rsp+88h] [rbp-18h] BYREF
  DWORD flOldProtect; // [rsp+94h] [rbp-Ch] BYREF
  BYTE *pbData; // [rsp+98h] [rbp-8h]

  if ( a2 == 1 )
  {
    MEMORY[0x20260058] = 0;
    MEMORY[0x20260000] = puts;
    MEMORY[0x20260008] = ExitProcess;
    MEMORY[0x20260060] = IsDebuggerPresent;
    MEMORY[0x20260068] = CryptAcquireContextA;
    MEMORY[0x20260070] = CryptImportKey;
    MEMORY[0x20260078] = CryptSetKeyParam;
    MEMORY[0x20260080] = CryptEncrypt;
    MEMORY[0x20260088] = CryptDestroyKey;
    MEMORY[0x20260090] = CryptReleaseContext;
    MEMORY[0x20260110] = 0x2CBC6E028388438ALL;
    MEMORY[0x20260118] = 0x5DB4CCED9A2C83F0LL;
    MEMORY[0x20260120] = 0x1861983A8A862DADLL;
    MEMORY[0x20260128] = 0x9331EA8FFA98EBB5uLL;
    MEMORY[0x20260130] = 0x589403358138DDB0LL;
    MEMORY[0x20260138] = 0x3E50A9A6549D13B6LL;
    strcpy(0x20260140, "Correct Flag");
    VirtualProtect(&BaseAddress, 0x1DA0uLL, 0x40u, &flOldProtect);
    pbData = GetModuleHandleA(0LL);
    phProv = 0LL;
    phHash = 0LL;
    hKey = 0LL;
    if ( CryptAcquireContextA(&phProv, 0LL, 0LL, 0x18u, 0xF0000000) )
    {
      if ( CryptCreateHash(phProv, 0x8003u, 0LL, 0, &phHash) )
      {
        CryptHashData(phHash, pbData, 0x80u, 0);
        pdwDataLen = 16;
        CryptGetHashParam(phHash, 2u, v12, &pdwDataLen, 0);
        v5[0] = 8;
        v5[1] = 2;
        v6 = 0;
        v7 = 26126;
        v8 = 16;
        v9 = *v12;
        v10 = v13;
        if ( CryptImportKey(phProv, v5, 0x1Cu, 0LL, 0, &hKey) )
        {
          *v4 = 2;
          CryptSetKeyParam(hKey, 4u, v4, 0);
          v3 = 7584;
          CryptDecrypt(hKey, 0LL, 0, 0, &BaseAddress, &v3);
          CryptDestroyKey(hKey);
        }
        CryptDestroyHash(phHash);
      }
      CryptReleaseContext(phProv, 0);
    }
    CurrentProcess = GetCurrentProcess();
    FlushInstructionCache(CurrentProcess, &BaseAddress, 0x1DA0uLL);
  }
}

It resolves Win32 API pointers into 0x20260000, then copies a 48-byte blob of data to that address:

8A438883026EBC2C F0832C9AEDCCB45D AD2D868A3A986118
B5EB98FA8FEA3193 B0DD38813503945 8B6139D54A6A9503E

I tried to use x64dbg to run the binary so I could see what those functions do: vault

vault

vault

What I understood is:

  • It hashes the first 0x80 bytes of the loaded EXE with MD5

I checked Microsoft documentation to understand the rest: CryptCreateHash | CryptAcquireContextA | CryptSetKeyParam | CryptExportKey

  • in CryptAcquireContextA: 0x18u represents PROV_RSA_AES
  • in CryptCreateHash: 0x8003u maps directly to CALG_MD5
  • in CryptGetHashParam: retrieves the MD5 hash buffer used as the AES key material
  • v7 = 26126; -> aiKeyAlg = CALG_AES_128 (0x660E)

So in the code flow after calculating the MD5 hash of the header, it uses that hash as the key for AES-ECB(we will know the mode later) to decrypt a 0x1DA0-byte region at BaseAddress

This decrypts the code and data in memory including the huge blob in the .data section

Now we have a clear view of what the challenge is about

Correct Flag Flow

The idea of the challenge is : setting any software breakpoint writes 0xCC (INT 3) into the binary and it modifies the first 0x80 bytes of the PE header, the MD5 hash changes, which corrupts the AES decryption key and crashes the program with an exception

So we can’t use any software breakpoints

Instead we used hardware breakpoints on the IsDebuggerPresent function and the strcpy call and run the binary with a dummy argument so we enter the correct path and hopefully see the flag

Finding the Correct Flag

To do so, I stopped x64dbg from setting software breakpoints on all TLS callbacks, placed a hardware breakpoints on IsDebuggerPresent and strcpy, and let the binary run

First I placed my hardware breakpoints while TLS callback event pausing was enabled: vault Then I removed the auto software breakpoints and kept only the hardware ones

vault I ran the debugger, but couldn’t hit IsDebuggerPresent. So I restarted and broke at TLS callbacks reaching TlsCallback_1 (it showes in x64dbg as TlsCallback_3), placed a hardware breakpoint on it, removed all auto breakpoints as before, and tried again vault

This huge calculation was the decryption algorithm, so I placed a hardware breakpoint after it and pressed F9: vault And watched memory at that point to see what was there: vault

which we will find out later this is the AES key

After a lot of tries, I couldn’t reach the strcpy I was waiting for and kept hitting an exception or a freeze

So I had to think of another way

I decided to decompile this huge function in IDA to make it easier to understand where the flag check happens

I set a hardware breakpoint in TlsCallback_1 (set a normal breakpoint, opened the Breakpoints table, right-clicked -> Edit -> changed type to Hardware Execute)

Then I added a command line argument (via Debugger -> Process options -> added dummy_input as an argument)

After that, I ran it and hit the breakpoint inside TlsCallback_1 then stepped over CryptDecrypt

Then checked the huge data blob inside .data, which was now decrypted

I sefined it with elected all of it, undU, converted it to code with C, and created a function with P

Here is what I got:

unsigned __int64 sub_7FF606FA9000()
{
  __int64 v0; // rsi
  unsigned __int64 v1; // r12
  __int64 v2; // rdx
  __int64 v3; // rbx
  unsigned __int64 v4; // r8
  unsigned __int64 v5; // rdi
  __int64 v6; // rax
  unsigned __int64 v7; // r11
  unsigned __int64 v8; // rbp
  unsigned __int64 v9; // r10
  unsigned __int64 v10; // rcx
  unsigned __int64 v11; // r9
  unsigned __int64 v12; // rsi
  unsigned __int64 v13; // rdx
  __int64 v14; // rbx
  unsigned __int64 v15; // rdi
  unsigned __int64 v16; // r11
  unsigned __int64 v17; // rax
  unsigned __int64 v18; // r8
  unsigned __int64 v19; // rbp
  unsigned __int64 v20; // rcx
  unsigned __int64 v21; // rsi
  unsigned __int64 v22; // r10
  unsigned __int64 v23; // rbx
  unsigned __int64 v24; // rdx
  unsigned __int64 v25; // r9
  unsigned __int64 v26; // rdi
  unsigned __int64 v27; // r11
  unsigned __int64 v28; // rax
  unsigned __int64 v29; // r8
  unsigned __int64 v30; // rbp
  unsigned __int64 v31; // r10
  unsigned __int64 v32; // rcx
  unsigned __int64 v33; // rsi
  unsigned __int64 v34; // rbx
  unsigned __int64 v35; // rdx
  unsigned __int64 v36; // r9
  unsigned __int64 v37; // rdi
  unsigned __int64 v38; // rax
  unsigned __int64 v39; // r11
  unsigned __int64 v40; // rbp
  unsigned __int64 v41; // r10
  unsigned __int64 v42; // rcx
  unsigned __int64 v43; // r8
  unsigned __int64 v44; // rsi
  unsigned __int64 v45; // rdx
  unsigned __int64 v46; // rbx
  unsigned __int64 v47; // rdi
  unsigned __int64 v48; // r11
  unsigned __int64 v49; // rax
  unsigned __int64 v50; // r9
  unsigned __int64 v51; // rbp
  unsigned __int64 v52; // rcx
  unsigned __int64 v53; // rsi
  unsigned __int64 v54; // r10
  unsigned __int64 v55; // rbx
  unsigned __int64 v56; // rdx
  unsigned __int64 v57; // r8
  unsigned __int64 v58; // rdi
  unsigned __int64 v59; // r11
  unsigned __int64 v60; // rax
  unsigned __int64 v61; // r9
  unsigned __int64 v62; // rsi
  unsigned __int64 v63; // r10
  unsigned __int64 v64; // rcx
  unsigned __int64 v65; // rbp
  unsigned __int64 v66; // rbx
  unsigned __int64 v67; // rdx
  unsigned __int64 v68; // r8
  unsigned __int64 v69; // rdi
  unsigned __int64 v70; // rax
  unsigned __int64 v71; // r11
  unsigned __int64 v72; // rsi
  unsigned __int64 v73; // rcx
  unsigned __int64 v74; // r10
  unsigned __int64 v75; // r9
  unsigned __int64 v76; // r12
  unsigned __int64 v77; // rdx
  unsigned __int64 v78; // rbx
  unsigned __int64 v79; // rdi
  unsigned __int64 v80; // rax
  unsigned __int64 v81; // rsi
  unsigned __int64 v82; // r8
  unsigned __int64 v83; // rbp
  unsigned __int64 v84; // rcx
  unsigned __int64 v85; // r11
  unsigned __int64 v86; // r10
  unsigned __int64 v87; // rbx
  unsigned __int64 v88; // rdx
  unsigned __int64 v89; // r9
  unsigned __int64 v90; // rdi
  unsigned __int64 v91; // rsi
  unsigned __int64 v92; // rax
  unsigned __int64 v93; // r8
  unsigned __int64 v94; // r10
  unsigned __int64 v95; // r11
  unsigned __int64 v96; // rcx
  unsigned __int64 v97; // r12
  unsigned __int64 v98; // rbx
  unsigned __int64 v99; // rdx
  unsigned __int64 v100; // r9
  unsigned __int64 v101; // rdi
  unsigned __int64 v102; // rax
  unsigned __int64 v103; // r10
  unsigned __int64 v104; // rbp
  unsigned __int64 v105; // rcx
  unsigned __int64 v106; // r11
  unsigned __int64 v107; // r8
  unsigned __int64 v108; // rsi
  unsigned __int64 v109; // rdx
  unsigned __int64 v110; // r9
  unsigned __int64 v111; // rdi
  unsigned __int64 v112; // rbx
  unsigned __int64 v113; // rax
  unsigned __int64 v114; // r10
  unsigned __int64 v115; // rbp
  unsigned __int64 v116; // rcx
  unsigned __int64 v117; // rsi
  unsigned __int64 v118; // r8
  unsigned __int64 v119; // r11
  unsigned __int64 v120; // rdx
  unsigned __int64 v121; // r9
  unsigned __int64 v122; // rdi
  unsigned __int64 v123; // rbx
  unsigned __int64 v124; // rax
  unsigned __int64 v125; // rcx
  unsigned __int64 v126; // rsi
  unsigned __int64 v127; // r10
  unsigned __int64 v128; // r8
  unsigned __int64 v129; // rbp
  unsigned __int64 v130; // r11
  unsigned __int64 v131; // rdx
  unsigned __int64 v132; // r9
  unsigned __int64 v133; // rdi
  unsigned __int64 v134; // rcx
  unsigned __int64 v135; // rbx
  unsigned __int64 v136; // rsi
  unsigned __int64 v137; // rax
  unsigned __int64 v138; // r10
  unsigned __int64 v139; // r8
  unsigned __int64 v140; // rbp
  unsigned __int64 v141; // rdx
  unsigned __int64 v142; // r11
  unsigned __int64 v143; // rdi
  unsigned __int64 v144; // r9
  unsigned __int64 v145; // rsi
  unsigned __int64 v146; // rcx
  unsigned __int64 v147; // rax
  unsigned __int64 v148; // r10
  unsigned __int64 v149; // r8
  unsigned __int64 v150; // rbx
  unsigned __int64 v151; // rdx
  unsigned __int64 v152; // rdi
  unsigned __int64 v153; // r11
  unsigned __int64 result; // rax

  v0 = (__ROL8__(0x56C82252074AF8A6LL, 5) + 0x14A9FEA74BE3A6DELL) ^ 0x6A09E667F3BCC908LL;
  v1 = (0xAB20066D38940427uLL * __ROL8__(0xA54FF53A5F1D36F1uLL, 27) + 0x34CAFDC015EFE9DCLL) ^ 0x3C6EF372FE94F82BLL;
  v2 = 0x3436138118ACD01LL * (__ROL8__(0x87E706D9FA5D42A2uLL, 26) ^ 0x1F83D9ABFBLL) - 0x5AB00AC5A0E2C90FLL;
  v3 = (__ROR8__(0x8256EA66EBADFFE8uLL, 15) + 3082918450LL) ^ 0x510E527FADE682D1LL;
  v4 = ((8 * v0) ^ 0xC291ED7C134393FEuLL) - 0x64FA9773D4C193E1LL;
  v5 = (0x6C0354B08AF423EBLL * __ROL8__(0x5BE0CD19137E2179LL, 16) + 0x6E8CD353D0E6FA15LL) ^ 0x1F83D9ABFB41BD6BLL;
  v6 = 0x164913AEFA2D522DLL * ((v1 >> 17) ^ __ROR8__(v0 + 0x1A2934C4B72DCA57LL, 27)) + 0x5BE0CD19137E2179LL;
  v7 = v0 + ((v2 << 6) ^ 0xDC3A03054A4F78BCuLL);
  v8 = ((v3 ^ 0x86CB43AC55BE2FA3uLL) - 0xB5ED72B2F399D4BLL * __ROL8__(v1, 23)) ^ 0x7834B93328CD37A8LL;
  v9 = v2 ^ ((v5 >> 27) + __ROR8__(v3 - 0x58F7EEE312785623LL, 19));
  v10 = v1 + 0x1BF268749AF259BFLL * ((v4 >> 20) ^ __ROL8__(v2 + 0x2BF0F855C97ED9D9LL, 30));
  v11 = v3 + ((v6 << 34) ^ ((v4 << 8) ^ 0xE03E62012DEF7100uLL | HIBYTE(v4) ^ 0xB0u));
  v12 = v4 ^ ((v7 ^ 0x53F3CBD86F8AA07FLL) - 0x569B53F9A8700F39LL * __ROL8__(v5, 12));
  v13 = v5 - 0x2DE8A7FDDD1B1269LL * ((v8 >> 13) ^ __ROR8__(v6 - 0x4A3039E59D18F7EBLL, 23));
  v14 = v6 ^ ((v10 >> 20) + __ROR8__(v7 + 0x88099669A8065C3LL, 30));
  v15 = v7 ^ ((v9 ^ 0xB83435F533D3351LL) + 0x3FC7DDB508F6079LL * __ROL8__(v8, 19));
  v16 = v10 ^ ((v12 >> 23) + __ROR8__(v9 - 0x500A58023D8F2E2BLL, 23));
  v17 = v8 - 0x35D858092971E525LL * ((v11 >> 16) ^ __ROR8__(v10 - 0x3F0EAFFD475486DDLL, 30));
  v18 = v9 + ((v13 << 30) ^ ((v11 << 12) ^ 0x25EFD6195101D000LL | (v11 >> 52) ^ 0x446));
  v19 = v11 ^ ((v14 ^ 0x6E2C6BAFCA6C895BLL) - 0x13F49C231CCEB417LL * __ROL8__(v12, 8));
  v20 = v12 - 0x5A3476F9EF500FE3LL * ((v15 >> 9) ^ __ROR8__(v13 + 0x6D87DF1CAEE96C1DLL, 19));
  v21 = v13 ^ ((v17 >> 16) + __ROL8__(v14 + 0x4329132E1BC4B07FLL, 30));
  v22 = v14 + ((v16 << 23) ^ ((v15 << 23) ^ 0x1E31CCF1F5800000LL | (v15 >> 41) ^ 0x25327D));
  v23 = v17 ^ ((v19 >> 19) + __ROR8__(v16 - 0x22F18E70E688575LL, 27));
  v24 = v15 - 0x771A01EC0C1AE61DLL * ((v18 >> 12) ^ __ROR8__(v17 + 0x46C8B3C2CBEEB70FLL, 26));
  v25 = v16 + ((v20 << 26) ^ ((v18 << 16) ^ 0xA7FB67D6249D0000uLL | HIWORD(v18) ^ 0x432A));
  v26 = v18 ^ ((v21 ^ 0x8725CE6FC36614E7uLL) + 0x27785FCBD2E9B5D9LL * __ROR8__(v19, 5));
  v27 = v20 ^ ((v24 >> 12) + __ROL8__(v21 - 0x7E592235600F273FLL, 26));
  v28 = v19 - 0x3448479D63918127LL * ((v22 >> 5) ^ __ROR8__(v20 + 0x159546A5CDD57047LL, 15));
  v29 = v21 + ((v23 << 19) ^ ((v22 << 27) ^ 0xB17909A0E8000000uLL | (v22 >> 37) ^ 0x2ACA54B));
  v30 = v22 ^ ((v25 ^ 0xA0A16E730435817BuLL) + 0x26EE3EA0E3873527LL * __ROR8__(v24, 16));
  v31 = v24 ^ ((v26 >> 15) + __ROR8__(v23 + 0x71DF60777ABEFF71LL, 31));
  v32 = v23 + ((v28 << 22) ^ ((v25 << 20) ^ 0x611C737FBB00000LL | (v25 >> 44) ^ 0xCE994));
  v33 = v25 ^ ((v27 ^ 0x95C587F23D1E2DB3uLL) - 0x3BC96EA93DB09911LL * __ROR8__(v26, 9));
  v34 = v28 ^ ((v30 >> 8) + __ROL8__(v27 - 0x41724F36132E8EEBLL, 22));
  v35 = v26 + 0x596116B8E83D59DBLL * ((v29 >> 36) ^ __ROR8__(v28 + 0x1BC73DD1F76EF2F9LL, 11));
  v36 = v27 + ((v31 << 15) ^ ((v29 << 31) ^ 0x3134F6BE80000000LL | (v29 >> 33) ^ 0x4A5775A));
  v37 = v29 ^ ((v32 ^ 0xDC48544951C000A3uLL) + 0x4103011B4D842A1FLL * __ROR8__(v30, 20));
  v38 = v30 + 0x54D35166622E4FFFLL * ((v33 >> 29) ^ __ROL8__(v31 - 0x41D63F3CD71E8CB3LL, 9));
  v39 = v31 + ((v35 << 18) ^ ((v32 << 24) ^ 0x1DBCCD81F3000000LL | (v32 >> 40) ^ 0x4786ED));
  v40 = v32 ^ ((v34 ^ 0xBB07E60D26D2282DuLL) + 0x2CC669B5107C5885LL * __ROR8__(v33, 13));
  v41 = v35 ^ ((v37 >> 4) + __ROL8__(v34 + 0x5151AE2E8275E45FLL, 18));
  v42 = v33 - 0x54BDAFAE8B8112CDLL * (HIDWORD(v36) ^ __ROR8__(v35 - 0x5F0AF8FCB72A9B0BLL, 7));
  v43 = v34 + ((v38 << 11) ^ ((v36 << 35) ^ 0x48B39D2800000000LL | (v36 >> 29) ^ 0x30F77FFD8LL));
  v44 = v36 ^ ((v39 ^ 0x7C77B4F5824FF00BLL) + 0x5CF22A9E109CEEF5LL * __ROR8__(v37, 24));
  v45 = v37 - 0x475DAFCDEBC34B1BLL * ((v40 >> 25) ^ __ROL8__(v38 - 0x63D8762F29865B73LL, 13));
  v46 = v38 ^ (HIDWORD(v42) + __ROL8__(v39 + 0x13586CB70C64A37FLL, 7));
  v47 = v39 ^ ((v41 ^ 0xE78E36E67D4898BDuLL) - 0x64F29CDFED5E6EB5LL * __ROR8__(v40, 17));
  v48 = v42 ^ ((v44 >> 35) + __ROL8__(v41 - 0x21CE924BD7F54039LL, 14));
  v49 = v40 - 0x2E0F74634726CD53LL * ((v43 >> 28) ^ __ROL8__(v42 - 0x4FA8C162A86A2A43LL, 6));
  v50 = v41 + ((v45 << 7) ^ ((v43 << 39) ^ 0x259AB8000000000LL | (v43 >> 25) ^ 0x1FE8AA69A4LL));
  v51 = v43 ^ ((v46 ^ 0x6FF1D8E87CF1679BLL) + 0x3103D94C869370C7LL * __ROR8__(v44, 28));
  v52 = v44 + 0x1400BC8318A229E1LL * ((v47 >> 21) ^ __ROL8__(v45 - 0x24F81C87707E220BLL, 17));
  v53 = v45 ^ ((v49 >> 28) + __ROR8__(v46 - 0x27A1132B6279476BLL, 6));
  v54 = v46 + ((v48 << 35) ^ ((v47 << 50) ^ 0x5E6C000000000000LL | (v47 >> 14) ^ 0x77C01B754BB9LL));
  v55 = v49 ^ ((v51 >> 31) + __ROL8__(v48 + 0x63D6FA204B4CCBCFLL, 10));
  v56 = v47 - 0x3DE77B2DC411B6D7LL * ((v50 >> 24) ^ __ROL8__(v49 - 0x4A15270858F43151LL, 10));
  v57 = v48 + ((8 * v52) ^ ((v50 << 43) ^ 0x62D9080000000000LL | (v50 >> 21) ^ 0xC8EC79B09LL));
  v58 = v50 ^ ((v53 ^ 0xE1EDCC2EE5877CF5uLL) + 0x7F64B1454043C031LL * __ROL8__(v51, 32));
  v59 = v52 ^ ((v56 >> 24) + __ROR8__(v53 + 0x1B2B41E896817A3FLL, 10));
  v60 = v51 - 0x4EA9B0FEA2E8677LL * ((v54 >> 17) ^ __ROL8__(v52 - 0x51BAF62164C74AE7LL, 21));
  v61 = v53 + ((v55 << 31) ^ ((v54 << 54) ^ 0x7FC0000000000000LL | (v54 >> 10) ^ 0xE9E64A1DBA285LL));
  v62 = v54 ^ ((v57 ^ 0x3A7D3AE4AB6D0135LL) - 0x71F42BDEAD0266DFLL * __ROL8__(v56, 21));
  v63 = v56 ^ ((v58 >> 27) + __ROL8__(v55 + 0x716AA0D9BEDF27E7LL, 6));
  v64 = v55 + ((v60 << 34) ^ ((v57 << 47) ^ 0xD866800000000000uLL | (v57 >> 17) ^ 0x7CBB7403E95ALL));
  v65 = v57 ^ ((v59 ^ 0x75B810328A55E935LL) - 0x53D54AFAC9AB2C5DLL * __ROL8__(v58, 28));
  v66 = v60 ^ ((v62 >> 20) + __ROR8__(v59 - 0x7866DC2A6617E8B3LL, 14));
  v67 = v58 + 0x392944951288DDD5LL * ((v61 >> 13) ^ __ROL8__(v60 - 0x8271C7F0322587LL, 25));
  v68 = v59 + ((v63 << 27) ^ ((v61 << 58) ^ 0x4400000000000000LL | (v61 >> 6) ^ 0x3501C909EFA44DELL));
  v69 = v61 ^ ((v64 ^ 0xADD987FBCFBC8789uLL) + 0x274B8B151001D861LL * __ROL8__(v62, 17));
  v70 = v62 - 0x6786B939D3163E71LL * ((v65 >> 6) ^ __ROR8__(v63 - 0x42C9207CB1A3B36BLL, 28));
  v71 = v63 + ((v67 << 30) ^ ((v64 << 51) ^ 0x8E98000000000000uLL | (v64 >> 13) ^ 0x4C0A33F8E0E1ALL));
  v72 = v64 ^ ((v66 ^ 0x44C3B37C8A96B51FLL) + 0x123ABCC4703757F7LL * __ROL8__(v65, 24));
  v73 = v65 - 0x1DB16CC1B0E6F585LL * ((v68 >> 9) ^ __ROL8__(v67 + 0x2BA0E13EA5C89655LL, 29));
  v74 = v67 ^ ((v69 >> 16) + __ROR8__(v66 + 0x10DC33DFAAB00B75LL, 18));
  v75 = v66 + ((v70 << 23) ^ ((v68 << 7) ^ 0x4AEFE328444AD980LL | (v68 >> 57) ^ 0x13));
  v76 = v68 ^ ((v71 ^ 0xFF6D82A6AC000253uLL) - 0x41CFACFCBAD2C979LL * __ROL8__(v69, 13));
  v77 = v69 + 0x2BB30A72DE0818AFLL * ((v72 >> 37) ^ __ROR8__(v70 + 0x5E667C9420146559LL, 24));
  v78 = v70 ^ ((v73 >> 9) + __ROR8__(v71 + 0x55DB5764DAA4BB3DLL, 29));
  v79 = v71 ^ ((v74 ^ 0xD3628CD7A909292FuLL) + 0x7E7ADD40C19AAC93LL * __ROL8__(v72, 20));
  v80 = v72 - 0x724D65335DF1CD57LL * ((v75 >> 5) ^ __ROR8__(v73 - 0x222BFE9FE544FCA3LL, 31));
  v81 = v73 ^ ((v76 >> 12) + __ROR8__(v74 + 0x2CCE39514AC15261LL, 22));
  v82 = v74 + ((v77 << 19) ^ ((v75 << 11) ^ 0x79D5A4B4C2D0B800LL | (v75 >> 53) ^ 0x268));
  v83 = v75 ^ ((v78 ^ 0xE2CA964167201681uLL) - 0x1E3B37B554EFB64FLL * __ROL8__(v76, 9));
  v84 = v76 + 0x54564FEEED4A2551LL * ((v79 >> 33) ^ __ROR8__(v77 - 0x5046941559EAB715LL, 20));
  v85 = v77 ^ ((v80 >> 5) + __ROL8__(v78 - 0x68A74482835F3C4FLL, 31));
  v86 = v78 + ((v81 << 12) ^ ((v79 << 22) ^ 0xCF104A9F2C00000LL | (v79 >> 42) ^ 0xAE70A));
  v87 = v80 ^ ((v83 >> 8) + __ROR8__(v81 - 0x441506D21F4A433BLL, 26));
  v88 = v79 + 0x466E860D0FF31427LL * ((v82 >> 36) ^ __ROR8__(v80 - 0x1A23B4649024AD97LL, 27));
  v89 = v81 + ((v84 << 15) ^ ((v82 << 15) ^ 0xD007C9E47B838000uLL | (v82 >> 49) ^ 0xF3B));
  v90 = v82 ^ ((v85 ^ 0xB967E40D850BCBA3uLL) + 0xD33663913B85E0BLL * __ROL8__(v83, 5));
  v91 = v84 ^ ((v88 >> 36) + __ROL8__(v85 + 0x686C6D7BEAEEE42BLL, 27));
  v92 = v83 - 0x1C1E0C294C803A33LL * ((v86 >> 29) ^ __ROR8__(v84 - 0x18413DF55DC482BBLL, 16));
  v93 = v85 + ((v87 << 8) ^ ((v86 << 26) ^ 0xE052D2B2CC000000uLL | (v86 >> 38) ^ 0x183F941));
  v94 = ((v89 ^ 0x6AD66761CA8D46DFLL) + 0x67A29D2AE791698BLL * __ROR8__(v88, 15)) ^ v86;
  v95 = v88 ^ ((v90 >> 4) + __ROR8__(v87 + 0x7DAA0738F41FFEFDLL, 30));
  v96 = v87 + ((v92 << 11) ^ ((v89 << 19) ^ 0x65F7116B0EB80000LL | (v89 >> 45) ^ 0x2182));
  v97 = v89 ^ ((v91 ^ 0x8DD5AA43B159CA6DuLL) + 0x44F5D4BDAB04962FLL * __ROR8__(v90, 8));
  v98 = v92 ^ (HIDWORD(v94) + __ROL8__(v91 - 0x56042A15CF52A2D3LL, 23));
  v99 = v90 + 0x6BDED3FD9663DC43LL * ((v93 >> 25) ^ __ROR8__(v92 + 0x17D9E1FC97627321LL, 12));
  v100 = v91 + ((16 * v95) ^ ((v93 << 30) ^ 0x3B19C144C0000000LL | (v93 >> 34) ^ 0x34A2DF12));
  v101 = v93 ^ ((v96 ^ 0x41EDB51CF17DAB83LL) + 0x524CACD6A49B9237LL * __ROR8__(v94, 19));
  v102 = v94 + 0x53D05B8B7CBC8017LL * ((v97 >> 18) ^ __ROL8__(v95 - 0x724ABB70191820C7LL, 8));
  v103 = v95 + ((v99 << 7) ^ ((v96 << 23) ^ 0xDDEB337AEA800000uLL | (v96 >> 41) ^ 0x1392F9));
  v104 = v96 ^ ((v98 ^ 0xCBA8229652C78F8BuLL) - 0x1D100DDFB91144BFLL * __ROR8__(v97, 12));
  v105 = v97 + 0x38320293A369F7DBLL * ((v100 >> 21) ^ __ROR8__(v99 - 0x24135E93B3A718FBLL, 8));
  v106 = v99 ^ ((v101 >> 28) + __ROL8__(v98 + 0x27E4300E1006E54DLL, 19));
  v107 = v98 + ((v102 << 35) ^ ((v100 << 34) ^ 0x7F9F32FC00000000LL | (v100 >> 30) ^ 0x32C47B367LL));
  v108 = v100 ^ ((v103 ^ 0xBD0672989896ADD7uLL) + 0x4DF074EB474A506BLL * __ROR8__(v101, 23));
  v109 = v101 - 0x5BC114B10B45A0B9LL * ((v104 >> 14) ^ __ROL8__(v102 + 0x5CF6F08E031F85BDLL, 12));
  v110 = v102 ^ ((v105 >> 21) + __ROL8__(v103 + 0x6007DC6C8DBFCC6DLL, 8));
  v111 = v103 ^ ((v106 ^ 0xF832DBB01D7235A3uLL) + 0x6DF7503ECB23D8BDLL * __ROR8__(v104, 16));
  v112 = v105 ^ ((v108 >> 24) + __ROL8__(v106 + 0x7B989A80E3463501LL, 15));
  v113 = v104 + 0x31C6B0E412439BE9LL * ((v107 >> 17) ^ __ROL8__(v105 - 0x7AECF4BC357F17F1LL, 5));
  v114 = v106 + ((v109 << 31) ^ ((v107 << 38) ^ 0x273063C000000000LL | (v107 >> 26) ^ 0x21D51AF686LL));
  v115 = v107 ^ ((v110 ^ 0x296D55015EEF7C79LL) - 0x567817B8E800CD13LL * __ROR8__(v108, 27));
  v116 = v108 - 0x6D033D7C0B08892BLL * ((v111 >> 10) ^ __ROL8__(v109 - 0x341BD58FF744A139LL, 16));
  v117 = v109 ^ ((v113 >> 17) + __ROR8__(v110 + 0x5DC03E25944143D3LL, 5));
  v118 = v110 + ((v112 << 24) ^ ((v111 << 49) ^ 0xA5B6000000000000uLL | (v111 >> 15) ^ 0x1055EC57416E2LL));
  v119 = v113 ^ ((v115 >> 20) + __ROL8__(v112 - 0xDF27BBAD3B71725LL, 11));
  v120 = v111 + 0x6F8593568B10BD89LL * ((v114 >> 13) ^ __ROL8__(v113 - 0x3197E91A03944E01LL, 9));
  v121 = v112 + ((v116 << 27) ^ ((v114 << 42) ^ 0x6797540000000000LL | (v114 >> 22) ^ 0x27E58DF5F54LL));
  v122 = v114 ^ ((v117 ^ 0xCC0DDAA68126764DuLL) - 0x45EE61AC6452AA19LL * __ROR8__(v115, 31));
  v123 = v116 ^ ((v120 >> 13) + __ROR8__(v117 - 0x73AA50E88F59E711LL, 9));
  v124 = v115 - 0x1EB822247C54AD19LL * ((v118 >> 6) ^ __ROL8__(v116 - 0x4950EACD1911C5DLL, 20));
  v125 = v117 + ((v119 << 20) ^ ((v118 << 53) ^ 0xF9A0000000000000uLL | (v118 >> 11) ^ 0x11138414182FB5LL));
  v126 = v118 ^ ((v121 ^ 0x54BA97AC147477ADLL) + 0x6B19818C073977B3LL * __ROL8__(v120, 22));
  v127 = v120 ^ ((v122 >> 16) + __ROL8__(v119 + 0x3AA50910BF83BD2BLL, 7));
  v128 = v119 + ((v124 << 23) ^ ((v121 << 46) ^ 0x6532C00000000000LL | (v121 >> 18) ^ 0x2A94E7962D8LL));
  v129 = v121 ^ ((v123 ^ 0x9DF638CB3685BC8FuLL) + 0x281CF62DCC142853LL * __ROL8__(v122, 29));
  v130 = v124 ^ ((v126 >> 9) + __ROR8__(v123 + 0x35CE27049C461B43LL, 13));
  v131 = v122 + 0x3B66916FBA3E53B3LL * ((v125 >> 37) ^ __ROL8__(v124 + 0x17D8F9B57B34C69DLL, 24));
  v132 = v123 + ((v127 << 16) ^ ((v125 << 57) ^ 0x9E00000000000000uLL | (v125 >> 7) ^ 0x17AE7A59CBB65A8LL));
  v133 = v125 ^ ((v128 ^ 0x146E91343EF65613LL) + 0xD8F65CA5E10AD9DLL * __ROL8__(v126, 18));
  v134 = v126 + 0x4CCB2F60093E3AEDLL * ((v129 >> 30) ^ __ROR8__(v127 + 0x27424735BC3509A3LL, 29));
  v135 = v127 + ((v131 << 19) ^ ((v128 << 50) ^ 0x60CC000000000000LL | (v128 >> 14) ^ 0x149ECB988C5E8LL));
  v136 = v128 ^ ((v130 ^ 0xD2839EB0BF82D103uLL) - 0x5D7EE79BF97E797DLL * __ROL8__(v129, 25));
  v137 = v129 - 0x69B73B3D381E319FLL * ((v132 >> 33) ^ __ROL8__(v131 - 0x6ED451BC34BFCA9DLL, 28));
  v138 = v131 ^ ((v133 >> 5) + __ROR8__(v130 + 0x418AB9A6311558FDLL, 17));
  v139 = v130 + ((v134 << 12) ^ ((v132 << 6) ^ 0xC8916E4427FE3C0LL | (v132 >> 58) ^ 0xD));
  v140 = v132 ^ ((v135 ^ 0x95406CFB40ED64A1uLL) + 0x774A71E594C00B19LL * __ROL8__(v133, 14));
  v141 = v133 + 0x2F37322C7CFE1D3DLL * ((v136 >> 26) ^ __ROR8__(v134 - 0x76AA227B839F7FD1LL, 25));
  v142 = v134 ^ ((v137 >> 33) + __ROR8__(v135 - 0x1D7B19EC730FBE11LL, 28));
  v143 = v135 ^ ((v138 ^ 0xBD2628AC44526E99uLL) + 0x615C50F384A4023DLL * __ROL8__(v136, 21));
  v144 = v136 + 0x2D8DE0F641EF87E3LL * ((v139 >> 29) ^ __ROL8__(v137 + 0x74D83C5A378AD2D1LL, 32));
  v145 = v137 ^ ((v140 >> 36) + __ROR8__(v138 + 0x50C27569028F2329LL, 21));
  v146 = v138 + ((v141 << 8) ^ ((v139 << 10) ^ 0x81A57ED3978FCC00uLL | (v139 >> 54) ^ 0x1EC));
  v147 = v139 ^ ((v142 ^ 0x876DF688FDBF76D7uLL) - 0x6098BB67E5F06A3FLL * __ROL8__(v140, 10));
  v148 = v140 + 0x6C9ED6231F90F283LL * ((v143 >> 22) ^ __ROR8__(v141 + 0x2DDC46424B6C79FDLL, 21));
  v149 = v141 ^ ((v144 >> 29) + __ROL8__(v142 - 0x7578ECA2ABDE6215LL, 32));
  v150 = v142 + ((v145 << 36) ^ ((v143 << 21) ^ 0xF7309C3A13600000uLL | (v143 >> 43) ^ 0x14E8CC));
  v151 = v143 + 0x39BC5AB9C4DFC571LL * ((v146 >> 25) ^ __ROR8__(v144 + 0x783B20D3FC59E5BFLL, 28));
  v152 = v144 ^ (HIDWORD(v147) + __ROR8__(v145 - 0x10279E87ED1490EDLL, 25));
  v153 = v145 + ((16 * v148) ^ ((v146 << 14) ^ 0x9BD840149B904000uLL | (v146 >> 50) ^ 0x2638));
  qword_20260010 = (v147 + 0x17DDA8E078EC6DF3LL * ((v150 >> 18) ^ __ROR8__(v148 - 0x4EA5A86F2DFD0081LL, 17))) ^ v153 ^ v151 ^ (v149 + ((v152 << 32) ^ ((v150 << 25) ^ 0xA7995752C2000000uLL | (v150 >> 39) ^ 0x1E51517)));
  result = ((v153 ^ 0x3EE9030A33EC6169LL) + 0x7C1624D2D1AC7C93LL * __ROR8__(v151, 14)) ^ ((v151 >> 25)
                                                                                        + __ROL8__(
                                                                                            v149 - 0x312EA921524D0D33LL,
                                                                                            28)) ^ v152 ^ v148 ^ v150 ^ v146 ^ ((v149 ^ 0xB3661EE145B34D45uLL) + 0x30139D7A38219B07LL * __ROL8__(v147, 6));
  qword_20260018 = result;
  return result;
}

So after inspecting the code, I noticed there is a 16-byte buffer produced at the end of this function at *0x20260010

This shall be an important value, so I grabbed it

vault

D4 BA 32 97 22 D3 63 73 B6 99 EB 54 23 FB F0 B7

I was stuck here for a while trying to figure out with x64dbg why it’s not working and kept freezing like it’s was in an infinite loop

I know there was a trap to the debugger but with scyllahide I shall be okay right?

I searched in reference for the “Correct Flag” appearance to see where shall be the last checks and this is the function that have it

vault After some analyze to it, I found it compares something at 0x20260028 with 0x20260110

Bytes at 0x20260110 are the ones from Tlscallback_1 which likely the encrypted flag

So I have the bytes at 0x20260028 and the 16 bytes recovered from the large function we counter previously

vault D4 BA 32 97 22 D3 63 73 B6 99 EB 54 23 FB F0 B7

So I opened cyberchef and stared playing with what I got till I found this

vault

alt text

This means we got the flag finally

Adding the correct enrypted flag instead of the dumy one and changing the mode to ECB (here is the recipe)

8A438883026EBC2CF0832C9AEDCCB45DAD2D868A3A986118B5EB98FA8FEA3193B0DD388135039458B6139D54A6A9503E

I got the flag

vault CATF{N0w_y0u_r34lly_f0und_th3_fl4g}

Devs beginning

7asabo After loading the evidence into FTK Imager and checking ConsoleHost_history.txt at C:\Users\7asabo\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadline\ConsoleHost_history.txt:

The challenge idea stode out 7asabo

Add-Type -AssemblyName System.Security
$keyBytes = [System.IO.File]::ReadAllBytes("C:\Users\7asbo\Desktop\confidential_repo_key")
$encrypted = [System.Security.Cryptography.ProtectedData]::Protect(`
    $keyBytes,`
    $null,`
    [System.Security.Cryptography.DataProtectionScope]::CurrentUser`
)
Add-Type -AssemblyName System.Security
$keyBytes = [System.IO.File]::ReadAllBytes("C:\Users\7asabo\Desktop\confidential_repo_key")
$encrypted = [System.Security.Cryptography.ProtectedData]::Protect(`
    $keyBytes,`
    $null,`
    [System.Security.Cryptography.DataProtectionScope]::CurrentUser`
)
cd .\Desktop\
mkdir project
cd .\project\
git init
echo "myrepo:git@github.com:7asabo16/confidential.git">private repo.txt
ls
echo "1-prepare a presentation about The Internship, 2-do the final project of the internship, 3-get ready for the new semester">Tasks.txt
cd ..
.\sdelete64.exe -p 3 -s C:\Users\7asabo\Desktop\project\ssh_key
.\sdelete64.exe -p 3 -s C:\Users\7asabo\Desktop\project\ssh_key.dpapi
cd .\project\
git add .\ssh_key.dpapi
git commit -m "back up key"
git config --global user.email "Eng7asabo12@outlook.com"
git config --global user.name "Eng7asabo"
git log
git commit -m "back up key"
ls
git add '.\private repo.txt'
git commit -m "dont share!!!"
ls
cd ..
.\sdelete64.exe -p3 '.\project\private repo.txt' .\project\ssh_key.dpapi
.\sdelete64.exe -p3 .\project\ssh_key.dpapi
.\sdelete64.exe -p3 C:\Users\7asabo\Desktop\project\ssh_key.dpapi
.\sdelete64.exe -p 3 C:\Users\7asabo\Desktop\project\ssh_key.dpapi
.\sdelete64.exe -p 3 'C:\Users\7asabo\Desktop\project\private repo.txt'
cd .\project\
ls
cd .\Desktop\
.\sdelete64.exe -p 3 .\script.ps1

So after checking the user 7asabo’s Desktop folder, I found a folder called project, and inside it was a Tasks.txt file and a .git folder 7asabo

After navigating to the project’s repository directory and checking the history, I found the two committed objects: 7asabo

Then I extracted them: 7asabo Now I had both the private repo URL myrepo:git@github.com:7asabo16/confidential.git and the ssh_key.dpapi file

We need to see what was inside that private repo, but to clone it we need access to it

Luckily we have ssh_key.dpapi, but it’s encrypted with 7asabo’s DPAPI MasterKey. To decrypt it, we need the master key located at C:\Users\7asabo\AppData\Roaming\Microsoft\Protect\S-1-5-21-1119876999-3187066875-582830896-1000 with MasterKey GUID 7abda562-0278-4847-81df-d4ce039112ae

To do so, I used Mimikatz:

  .#####.   mimikatz 2.2.0 (x64) #19041 Sep 19 2022 17:44:08
 .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 ## \ / ##       > https://blog.gentilkiwi.com/mimikatz
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )
  '#####'        > https://pingcastle.com / https://mysmartlogon.com ***/

mimikatz # privilege::debug
ERROR kuhl_m_privilege_simple ; RtlAdjustPrivilege (20) c0000061

mimikatz # lsadump::sam /sam:C:\Users\Flare\Desktop\Evidence-extracted\SAM /system:C:\Users\Flare\Desktop\Evidence-extracted\SYSTEM
Domain : WIN-37DI7ORCIS9
SysKey : 156b633468c51f81335669a61fb6d07f
Local SID : S-1-5-21-1119876999-3187066875-582830896

SAMKey : f2520e176678d6e9020dbc380ef9ff9d

RID  : 000001f4 (500)
User : Administrator

RID  : 000001f5 (501)
User : Guest

RID  : 000001f7 (503)
User : DefaultAccount

RID  : 000001f8 (504)
User : WDAGUtilityAccount
  Hash NTLM: 0832c6325672f2a9c820660c2399e205

Supplemental Credentials:
* Primary:NTLM-Strong-NTOWF *
    Random Value : 68b06b65630a84b4df542741857ce4ea

* Primary:Kerberos-Newer-Keys *
    Default Salt : WDAGUtilityAccount
    Default Iterations : 4096
    Credentials
      aes256_hmac       (4096) : c8bef75f2e4764056eabe5fdbf781e4d60b25f0a9de291f0cfa0bb41731a49eb
      aes128_hmac       (4096) : c33f6b2600aff414dab566d3becd2aa2
      des_cbc_md5       (4096) : d9dcb54543f14580

* Packages *
    NTLM-Strong-NTOWF

* Primary:Kerberos *
    Default Salt : WDAGUtilityAccount
    Credentials
      des_cbc_md5       : d9dcb54543f14580


RID  : 000003e8 (1000)
User : 7asabo
  Hash NTLM: 068689e40a6b94bc3d431b454c18d6b9

Supplemental Credentials:
* Primary:NTLM-Strong-NTOWF *
    Random Value : f85c44c227ea916c9f3b1a2ccf173a7e

* Primary:Kerberos-Newer-Keys *
    Default Salt : WIN-37DI7ORCIS97asabo
    Default Iterations : 4096
    Credentials
      aes256_hmac       (4096) : 06a9f9352e54952864bdc15ee71cc82e5595b52c7b5f285c2e0089bee9679cb3
      aes128_hmac       (4096) : 97f106a6500672d2ae8eef4b77699725
      des_cbc_md5       (4096) : 8f6101e61008153e

* Packages *
    NTLM-Strong-NTOWF

* Primary:Kerberos *
    Default Salt : WIN-37DI7ORCIS97asabo
    Credentials
      des_cbc_md5       : 8f6101e61008153e

Doing so we have recovered 7asabo's Hash NTLM: 068689e40a6b94bc3d431b454c18d6b9

Now let’s dump the Dpapi key

mimikatz # lsadump::secrets /security:C:\Users\Flare\Desktop\Evidence-extracted\SECURITY /system:C:\Users\Flare\Desktop\Evidence-extracted\SYSTEM
Domain : WIN-37DI7ORCIS9
SysKey : 156b633468c51f81335669a61fb6d07f

Local name : WIN-37DI7ORCIS9 ( S-1-5-21-1119876999-3187066875-582830896 )
Domain name : WORKGROUP

Policy subsystem is : 1.18
LSA Key(s) : 1, default {4e377552-0f30-c69b-39b6-a11b34f71090}
  [00] {4e377552-0f30-c69b-39b6-a11b34f71090} 5a2dcc13d055a7cd3feed2acb8d13edca277ceed1c1f937f4001e607d3c69e29

Secret  : DefaultPassword
cur/text: mysecretpassword

Secret  : DPAPI_SYSTEM
cur/hex : 01 00 00 00 9f 94 65 f6 1c b6 0d 78 8b 8c 2d b3 50 f9 c5 a8 ca bf b4 1b c1 d9 53 3c b3 29 e0 fd d7 30 13 d8 83 9a c9 58 4c 2e f1 f8
    full: 9f9465f61cb60d788b8c2db350f9c5a8cabfb41bc1d9533cb329e0fdd73013d8839ac9584c2ef1f8
    m/u : 9f9465f61cb60d788b8c2db350f9c5a8cabfb41b / c1d9533cb329e0fdd73013d8839ac9584c2ef1f8
old/hex : 01 00 00 00 ba 5f ed ef 3e 11 4d ef f7 09 73 33 f1 de e9 18 8b f3 63 0f a0 64 66 d8 80 ef 6d e7 78 a3 6f e7 2c e4 29 0a 78 61 7b 10
    full: ba5fedef3e114deff7097333f1dee9188bf3630fa06466d880ef6de778a36fe72ce4290a78617b10
    m/u : ba5fedef3e114deff7097333f1dee9188bf3630f / a06466d880ef6de778a36fe72ce4290a78617b10

Secret  : NL$KM
cur/hex : 45 7b 4c 6d 84 97 4e c8 d6 6e 44 9f 08 97 88 b6 ea 05 27 59 90 44 da f1 4e 19 d1 30 dc c8 2d 45 97 cb 49 9d 0c c4 10 5c 96 9e 41 37 8e dc d2 61 71 e2 92 74 03 b3 c4 38 77 6b 29 ea 8a 1b 1b 45
old/hex : 45 7b 4c 6d 84 97 4e c8 d6 6e 44 9f 08 97 88 b6 ea 05 27 59 90 44 da f1 4e 19 d1 30 dc c8 2d 45 97 cb 49 9d 0c c4 10 5c 96 9e 41 37 8e dc d2 61 71 e2 92 74 03 b3 c4 38 77 6b 29 ea 8a 1b 1b 45

mimikatz #

Doing so we have recovered the Dpapi keys for machine and user ` m/u : 9f9465f61cb60d788b8c2db350f9c5a8cabfb41b / c1d9533cb329e0fdd73013d8839ac9584c2ef1f8`

and the user password mysecretpassword Now to find the master key we need, we can use either of those approches:

dpapi::masterkey /in:"C:\Users\Flare\Desktop\Evidence-extracted\7abda562-0278-4847-81df-d4ce039112ae" /sid:S-1-5-21-1119876999-3187066875-582830896-1000 /password:mysecretpassword

Or using the NTLM hash

dpapi::masterkey /in:"C:\Users\Flare\Desktop\Evidence-extracted\7abda562-0278-4847-81df-d4ce039112ae" /sid:S-1-5-21-1119876999-3187066875-582830896-1000 /hash:068689e40a6b94bc3d431b454c18d6b9

And this is what we got

mimikatz # dpapi::masterkey /in:"C:\Users\Flare\Desktop\Evidence-extracted\7abda562-0278-4847-81df-d4ce039112ae" /sid:S-1-5-21-1119876999-3187066875-582830896-1000 /hash:068689e40a6b94bc3d431b454c18d6b9
**MASTERKEYS**
  dwVersion          : 00000002 - 2
  szGuid             : {7abda562-0278-4847-81df-d4ce039112ae}
  dwFlags            : 00000005 - 5
  dwMasterKeyLen     : 000000b0 - 176
  dwBackupKeyLen     : 00000090 - 144
  dwCredHistLen      : 00000014 - 20
  dwDomainKeyLen     : 00000000 - 0
[masterkey]
  **MASTERKEY**
    dwVersion        : 00000002 - 2
    salt             : 951c4b1f1384b238979fd099429a957b
    rounds           : 00001f40 - 8000
    algHash          : 0000800e - 32782 (CALG_SHA_512)
    algCrypt         : 00006610 - 26128 (CALG_AES_256)
    pbKey            : a07b2563fd708c7feeb3da07bf3142680e1919f829d4ac6287cf808618ac3df662151f92db36f08e58f9b49d752eac78c98dd2cb7bea3cd381f8d65bfaf88b402e25ccf28353db89dbe841a7ea9e736f657c040d17b9f2db026ed23c20c0b35eda4090c417cc94bc0f8ed07898063fe7912548eb9254917ef68740fb70d654145ec5741f0c21e3345987280e53eb6952

[backupkey]
  **MASTERKEY**
    dwVersion        : 00000002 - 2
    salt             : c3facbc2b0be497e7cba300033e22bf3
    rounds           : 00001f40 - 8000
    algHash          : 0000800e - 32782 (CALG_SHA_512)
    algCrypt         : 00006610 - 26128 (CALG_AES_256)
    pbKey            : b84b3da09b24dfb0344356d4c2a97a3e2cae8825f2052c85f89778673b2869da92d5d4e8ff4851ec874f5802023d953a117b16df918bec357451699bd6f3814628f93bd35a221d34203ab191cccf0cf8b009e4800e6f40a60e15e30d72bc629ce64a80a13503332edf5eef699982a11b

[credhist]
  **CREDHIST INFO**
    dwVersion        : 00000003 - 3
    guid             : {aa0b5148-71c0-437d-b001-a1f374a762dc}



[masterkey] with volatile cache: SID:S-1-5-21-1119876999-3187066875-582830896-1000;GUID:{aa0b5148-71c0-437d-b001-a1f374a762dc};MD4:068689e40a6b94bc3d431b454c18d6b9;SHA1:a5f1e811f41d623b4580aee922b5ee85df8627a2;
  key : bc5a44b02fb17f6016294151a81b754ff0505d633d1caafe2f2090c8f5495ce9595c8433da39ec12d73abe328e840974f20d61594023ea73daa8aa8e6ec80224
  sha1: b2a5761871ba49fefca4e0e59ded1cd8c7da6629

[masterkey] with hash: 068689e40a6b94bc3d431b454c18d6b9 (ntlm type)
ERROR kuhl_m_dpapi_masterkey ; kull_m_dpapi_unprotect_masterkey_with_userHash

mimikatz # dpapi::masterkey /in:"C:\Users\Flare\Desktop\Evidence-extracted\7abda562-0278-4847-81df-d4ce039112ae" /sid:S-1-5-21-1119876999-3187066875-582830896-1000 /password:mysecretpassword
**MASTERKEYS**
  dwVersion          : 00000002 - 2
  szGuid             : {7abda562-0278-4847-81df-d4ce039112ae}
  dwFlags            : 00000005 - 5
  dwMasterKeyLen     : 000000b0 - 176
  dwBackupKeyLen     : 00000090 - 144
  dwCredHistLen      : 00000014 - 20
  dwDomainKeyLen     : 00000000 - 0
[masterkey]
  **MASTERKEY**
    dwVersion        : 00000002 - 2
    salt             : 951c4b1f1384b238979fd099429a957b
    rounds           : 00001f40 - 8000
    algHash          : 0000800e - 32782 (CALG_SHA_512)
    algCrypt         : 00006610 - 26128 (CALG_AES_256)
    pbKey            : a07b2563fd708c7feeb3da07bf3142680e1919f829d4ac6287cf808618ac3df662151f92db36f08e58f9b49d752eac78c98dd2cb7bea3cd381f8d65bfaf88b402e25ccf28353db89dbe841a7ea9e736f657c040d17b9f2db026ed23c20c0b35eda4090c417cc94bc0f8ed07898063fe7912548eb9254917ef68740fb70d654145ec5741f0c21e3345987280e53eb6952

[backupkey]
  **MASTERKEY**
    dwVersion        : 00000002 - 2
    salt             : c3facbc2b0be497e7cba300033e22bf3
    rounds           : 00001f40 - 8000
    algHash          : 0000800e - 32782 (CALG_SHA_512)
    algCrypt         : 00006610 - 26128 (CALG_AES_256)
    pbKey            : b84b3da09b24dfb0344356d4c2a97a3e2cae8825f2052c85f89778673b2869da92d5d4e8ff4851ec874f5802023d953a117b16df918bec357451699bd6f3814628f93bd35a221d34203ab191cccf0cf8b009e4800e6f40a60e15e30d72bc629ce64a80a13503332edf5eef699982a11b

[credhist]
  **CREDHIST INFO**
    dwVersion        : 00000003 - 3
    guid             : {aa0b5148-71c0-437d-b001-a1f374a762dc}



[masterkey] with password: mysecretpassword (normal user)
  key : bc5a44b02fb17f6016294151a81b754ff0505d633d1caafe2f2090c8f5495ce9595c8433da39ec12d73abe328e840974f20d61594023ea73daa8aa8e6ec80224
  sha1: b2a5761871ba49fefca4e0e59ded1cd8c7da6629

And to recover the encrypted ssh_key we can use mimikatz

dpapi::blob /in:"C:\Users\Flare\Desktop\Evidence-extracted\recovered_ssh_key.dpapi" /masterkey:bc5a44b02fb17f6016294151a81b754ff0505d633d1caafe2f2090c8f5495ce9595c8433da39ec12d73abe328e840974f20d61594023ea73daa8aa8e6ec80224 

The output was

mimikatz # dpapi::blob /in:"C:\Users\Flare\Desktop\Evidence-extracted\recovered_ssh_key.dpapi" /masterkey:bc5a44b02fb17f6016294151a81b754ff0505d633d1caafe2f2090c8f5495ce9595c8433da39ec12d73abe328e840974f20d61594023ea73daa8aa8e6ec80224
**BLOB**
  dwVersion          : 00000001 - 1
  guidProvider       : {df9d8cd0-1501-11d1-8c7a-00c04fc297eb}
  dwMasterKeyVersion : 00000001 - 1
  guidMasterKey      : {7abda562-0278-4847-81df-d4ce039112ae}
  dwFlags            : 00000000 - 0 ()
  dwDescriptionLen   : 00000002 - 2
  szDescription      :
  algCrypt           : 00006610 - 26128 (CALG_AES_256)
  dwAlgCryptLen      : 00000100 - 256
  dwSaltLen          : 00000020 - 32
  pbSalt             : 5b4a574aeba9a2c26c7054dd24a450547540085abe527a4b220e634e4ee5da51
  dwHmacKeyLen       : 00000000 - 0
  pbHmackKey         :
  algHash            : 0000800e - 32782 (CALG_SHA_512)
  dwAlgHashLen       : 00000200 - 512
  dwHmac2KeyLen      : 00000020 - 32
  pbHmack2Key        : 14cffba6778962bad37cdb9c5ebcdd05937f701d8bd0c881c08286b9b240e8af
  dwDataLen          : 000001a0 - 416
  pbData             : a06228d0f2987b917891a0c928aa82d6a77331227468377af5246039cbd148d18bfcffdca50be55eaf050931d50bec0495a548ef990cee1dc905e6365eba68e94d1cde58c83a4fe8d2c27565f53ce291afcff7bb73daa8e7c1ac2a5dcc6eede77b3c116c14c85758665ae9b58c421d529ce2d295f142b07f3c36e6b98daaec9a2be4df8f210a23cc28f8f1b6fbca8fefe4fe6f4f3a37a6937abe0fe9a54b05f46d660d149536c5c5f3091a5d1e1687deec948df0a6d0e227ddbb38a49e3c489bb18ce8f2d21b13a8b968734e0911abe0da768eb8dc21d04c9fd406a70480bf01600b55f4a399bae8cbc1ef298b76437d165c2349b5b0bdb561340c1a313b5dc8218b5a505c97f3d6c873dddc94216aee86ee050cbb5308469e2873b8a461a554baf3c85f28a50b725fbe265da82f8251bac68d000a1c1edc51ab06f9a518ba8c223b9e23669f89737bc657432624e5ebec725cb53c24019563d76d9709892e9c2cffc0e30de786072a6de3df75161b84615d8d47f286517db059bcd2340e2dd3a98770a3f0abecfff90b03a7a67e7e7c35850786aac417161dac419b19f49a4b
  dwSignLen          : 00000040 - 64
  pbSign             : d5566d7fa247d36e081ed89a3e08c7e6f7d7a6583feec106faf4b8f4eae6960c5436ccc1a4760d866d16ca4ed5fe612c6623a2473479bfbd7b4f1fd29c6f6979

 * volatile cache: GUID:{7abda562-0278-4847-81df-d4ce039112ae};KeyHash:b2a5761871ba49fefca4e0e59ded1cd8c7da6629;Key:available
 * masterkey     : bc5a44b02fb17f6016294151a81b754ff0505d633d1caafe2f2090c8f5495ce9595c8433da39ec12d73abe328e840974f20d61594023ea73daa8aa8e6ec80224
description :
data: 2d 2d 2d 2d 2d 42 45 47 49 4e 20 4f 50 45 4e 53 53 48 20 50 52 49 56 41 54 45 20 4b 45 59 2d 2d 2d 2d 2d 0a 62 33 42 6c 62 6e 4e 7a 61 43 31 72 5a 58 6b 74 64 6a 45 41 41 41 41 41 42 47 35 76 62 6d 55 41 41 41 41 45 62 6d 39 75 5a 51 41 41 41 41 41 41 41 41 41 42 41 41 41 41 4d 77 41 41 41 41 74 7a 63 32 67 74 5a 57 0a 51 79 4e 54 55 78 4f 51 41 41 41 43 42 77 34 46 36 4a 32 2b 7a 31 71 7a 50 4c 36 55 38 61 68 4c 74 48 57 47 33 68 79 71 77 50 32 55 30 4f 72 73 31 31 42 65 4d 52 37 67 41 41 41 4a 68 7a 72 5a 68 32 63 36 32 59 0a 64 67 41 41 41 41 74 7a 63 32 67 74 5a 57 51 79 4e 54 55 78 4f 51 41 41 41 43 42 77 34 46 36 4a 32 2b 7a 31 71 7a 50 4c 36 55 38 61 68 4c 74 48 57 47 33 68 79 71 77 50 32 55 30 4f 72 73 31 31 42 65 4d 52 37 67 0a 41 41 41 45 42 58 48 4d 7a 4f 31 62 58 4d 43 6b 76 65 68 78 74 68 65 53 74 48 35 49 73 75 68 4b 46 6a 61 52 2f 54 43 42 41 36 6b 44 45 41 50 33 44 67 58 6f 6e 62 37 50 57 72 4d 38 76 70 54 78 71 45 75 30 64 59 0a 62 65 48 4b 72 41 2f 5a 54 51 36 75 7a 58 55 46 34 78 48 75 41 41 41 41 45 31 52 6f 61 58 4d 67 63 6d 56 77 62 79 42 70 63 79 42 7a 5a 57 4e 79 5a 58 51 42 41 67 3d 3d 0a 2d 2d 2d 2d 2d 45 4e 44 20 4f 50 45 4e 53 53 48 20 50 52 49 56 41 54 45 20 4b 45 59 2d 2d 2d 2d 2d 0a

mimikatz #

Then I used CyberChef to save the private key so I could connect to the private repo with it 7asabo

Then using this command

GIT_SSH_COMMAND='ssh -i "$PWD/private_key" -o IdentitiesOnly=yes' git clone git@github.com:7asabo16/confidential.git ./confidential_repo

I cloned the repo successfully and got the flag 7asabo CATF{Wh4t_A_G1t_Sk1ll_Y0u_H4v3}

Nightjar_FINWKS07

al4m

Questions and description

peration Nightjar (FINWKS07) - DFIR Investigation
==================================================

A workstation on the ARC corporate network was flagged after a network sensor
logged repeated failed name lookups originating from an accounting endpoint in
the small hours of the morning. The host was pulled and a triage image was
collected. Somewhere in this collection is a program that has no business being
where it is, wearing a coat that belongs to something else. It keeps its secrets
folded inside a file that was never meant to be read as text, wakes itself
without a single entry in the places you were taught to check first, and has
been calling home to a name that does not answer.

Nothing on this host tells you where to look. Public reporting exists on the
technique this intruder borrowed; it is not always correct, and it knows nothing
about this host, so trust it for nothing you can measure yourself. Work the
evidence, scope the intrusion end to end, and determine exactly what it did,
when, and how far it got.

Answer all questions to retrieve the flag.


Q1-One executable collected from this host is not what it claims to be. Give its SHA-256.


Q2-Give the full domain (including TLD) this host was configured to contact.


Q3-Give the exact URI path the implant uses to POST to that destination.


Q4-Give the name of the persistence entry the attacker registered on the host.


Q5-The implant runs once per day. How many minutes after local midnight is it set to trigger?


Q6-At what UTC time was the persistence installed on this host?


Q7-Give the exact size in bytes of the file that holds the command-and-control configuration.


Q8-Give the SHA-256 of that file.


Q9-When did the implant first execute on this host? Answer in UTC.


Q10-How many seconds elapsed between the implant's compilation and the staging of its configuration on this host?


Q11-How many times did the implant successfully reach its command-and-control destination?

Reading the prompt gives us several key clues:

  • we are searching for a program that masquerading as something else
  • It stores its C2 secrets inside a file not meant to be read as text
  • It achieves persistence through a non-traditional method (“wakes itself without a single entry in the places you were taught to check first”)

We were provided with a triage collection containing the filesystem image and a Modules folder with parsed artifacts:

20260731061204_Amcache_UnassociatedFileEntries.csv
EvtxECmd_Output.csv
20260731061204_LECmd_Output.csv
20260731061204_FINWKS07_J_Output.csv
20260731061204_FINWKS07_MFT_Output.csv
20260731061204_PECmd_Output.csv
20260731061204_RECmd_Batch_Kroll_Autoruns_Output.csv
20260731061204_SrumECmd_NetworkUsages_Output.csv

Let’s solve the investigation questions one by one.

Q1: One executable collected from this host is not what it claims to be. Give its SHA-256.

Loading the triage image into FTK Imager and checking user activity, under khaled.allam’s Recent items we see RealtekAudioDriver.zip.lnk:

al4m

Navigating to C:\Program Files\Realtek\Audio\, we find an executable named RtkNGUI64.exe. While this filename typically corresponds to the legitimate Realtek Audio utility, inspecting the binary shows it is unsigned and flagged as malicious on VirusTotal

al4m

Its SHA-256 hash:

9b32b09bdde7d5749ac0ffadae4af2d04cdd237e95aa88fb700ab79b0e9ca804

Answer: 9b32b09bdde7d5749ac0ffadae4af2d04cdd237e95aa88fb700ab79b0e9ca804

There were some blogs discussing this incident, like this one, but as mentioned in the description, we shouldn’t rely blindly on them

Q2: Give the full domain (including TLD) this host was configured to contact.

We have two ways:

  1. Triage Event Logs: Inspecting the Event Logs for RtkNGUI64.exe activity, I found: al4m This looks like an interesting persistence mechanism, and inspecting around it revealed a suspicious domain it was communicating with: al4m

  2. Reversing the malware: To recover where it hides its secrets. We find out that it stores its secrets inside C:\ProgramData\desktop.ini in a hidden way. This confirms the C2 server address, and I will detail the reversing process in the next question.

Answer: trustedinstaler.com

Q3: Give the exact URI path the implant uses to POST to that destination.

Loading RtkNGUI64.exe in IDA Pro and inspecting its functions, I found a function that performs some encoding or obfuscating using a repeating 4-byte XOR key 13 37 AC 5E al4m al4m

Basically, it is a simple XOR on those bytes, so I extracted them and decoded them using this CyberChef recipe:

  • Bytes at 0x4020: 7012DF7B60528C717017892D
  • Bytes at 0x402C: 3D54C333
  • Bytes at 0x4030: 500DF00E6158CB2C725AE83F6756F03A7644C72A7C4782377D5E
  • Bytes at 0x4060: 5E58D6377F5BCD7126199C7E3B60C5307758DB2D3379F87E2207826E2817FB377D019865334F9A6A3A17ED2E635BC9097655E7376718996D24199F68331FE716477AE072335BC5357617EB3B705CC3773374C42C7C5AC971240F826E3D04956E27199D6E2B17FF3F7556DE373C029F693D049A
  • Bytes at 0x40D8: 660A892D355391307C59C9
  • Bytes at 0x40F0: 3C43C932765AC92A614E833D7C5BC03B7043822E7B47

Deobfuscating these reveals:

  • c%s%se /c %s
  • .com
  • C:\ProgramData\desktop.ini
  • Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.10
  • u=%s&d=none
  • /telemetry/collect.php

The implant sends HTTP POST requests to /telemetry/collect.php

Answer: /telemetry/collect.php

Q4: Give the name of the persistence entry the attacker registered on the host.

As the description states, the malware wakes itself without a single entry in the places you were taught to check first (no standard Run keys, Services, or Scheduled Tasks)

Earlier, while searching EvtxECmd_Output.csv for RtkNGUI64.exe, we encountered this persistence entry:

ESS namespace=//./root/subscription
Filter: __EventFilter.Name="RtkAudioSvc"
Consumer: CommandLineEventConsumer.Name="RtkAudioMon"
Query: SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_LocalTime' AND TargetInstance.Hour = 3 AND TargetInstance.Minute = 17
CommandLineTemplate: C:\Progra~1\Realtek\Audio\RtkNGUI64.exe

So the answer shall be Answer: RtkAudioSvc

Q5: The implant runs once per day. How many minutes after local midnight is it set to trigger?

From the WMI query we found: al4m

3 hours and 17 minutes translates to 197 minutes

Answer: 197

Q6: At what UTC time was the persistence installed on this host?

al4m

Answer: 2026-07-14 12:07:10

Q7: Give the exact size in bytes of the file that holds the command-and-control configuration.

al4m

Answer: 3602

Q8: Give the SHA-256 of that file.

Answer: a83f2ff251867a80954b4f99be8d52fb41107aa22d44dfd22a557402ba792f3c

Q9: When did the implant first execute on this host? Answer in UTC.

Checking 20260731061204_PECmd_Output.csv for RtkNGUI64.exe:

al4m

This process ran 6 times:

  1. 2026-07-15 00:17:04.0000000
  2. 2026-07-18 00:17:02.0000000
  3. 2026-07-22 00:17:05.0000000
  4. 2026-07-25 00:17:01.0000000
  5. 2026-07-28 00:17:06.0000000
  6. 2026-07-30 00:17:03.0000000

So the answer shall be Answer: 2026-07-15 00:17:04

Q10: How many seconds elapsed between the implant’s compilation and the staging of its configuration on this host?

Inspecting the PE header to get its compilation timestamp: al4m The compilation time was 2026-07-14 09:22:41

Then i checked the MFT records to find the time creation of ‘C:\ProgramData\desktop.ini’: al4m The creation time was 2026-07-14 12:05:33

Calculating the difference Answer: 9772

Q11: How many times did the implant successfully reach its command-and-control destination?

Checking network activity in 20260731061204_SrumECmd_NetworkUsages_Output.csv and other logs to see if it was able to reach trustedinstaler[.]com: al4m

but it was never able to reach successfully, so the answer shall be

Answer: 0

After answering all 11 questions on the server successfully, we retrieve the flag: al4m

CATF{b16e21785bcdd2394b3e2a77de0f8eaa}

Conclusion

Overall, it was a really nice journey and a memorable experience. I’m truly thankful for all my teammates and their hard work. A special thanks to the authors for the effort they put into creating all these awesome challenges.

اللهم انفعنا بما علَّمْتَنا، وعلِّمنا ما ينفعنا، وزدنا علمًا وفقهًا وفهمًا

Categories:

Updated: