WHY2025 CTF | Full Forensics + Reversing Bonus

WHY2025 CTF | Full Forensics + Reversing Bonus

Forensics Index

forensics/Painted Black

Detecting Macro

We have given a Microsoft Word 2007+ document, as the extension is .docm being a standart of Microsoft Word Macro-Enabled. Knowing this we apply the tools suite from olevba, to be able to list all macros inside the file:

$> olevba case-2025-0412-public.docm -ac --deobf
olevba 0.60.2 on Python 3.11.2 - http://decalage.info/python/oletools
===============================================================================
FILE: case-2025-0412-public.docm
Type: OpenXML
WARNING  For now, VBA stomping cannot be detected for files in memory
-------------------------------------------------------------------------------
VBA MACRO ThisDocument.cls
in file: word/vbaProject.bin - OLE stream: 'VBA/ThisDocument'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Sub PaintItBlack()
    Dim selectedText As String
    Dim encryptedText As String
    Dim key As String
    Dim xorChar, keyPos

    selectedText = Selection.Text
    key = Replace(LCase(Application.UserName), " ", "")
    encryptedText = ""

    For i = 1 To Len(selectedText)
        keyPos = i Mod Len(key) + 1
        xorChar = Asc(Mid(selectedText, i, 1)) Xor Asc(Mid(key, keyPos, 1)) Xor &H7B
        encryptedText = encryptedText & Chr(xorChar)
    Next i

    Selection.Text = encryptedText
    Selection.Shading.BackgroundPatternColor = wdColorBlack
    Selection.Font.ColorIndex = wdBlack
End Sub

Retrieving encrypted content

At first glance, while reading the script, we realize that it is harmless and is simply decrypting certain parts of the document using the author’s name. To understood more the code, we open the file in a online docm viewer such as https://products.groupdocs.app/viewer/docm:

![](Pasted image 20250812235825.png)

By doing hovering into the painted text, some xored data will be found, also it is possible to identify it if you toggle to dark mode the file content, function that can be done with extensions like Black Reader:

![](Pasted image 20250813000128.png)

To obtain the raw content of the painted text, I used the command docx2txt:

$> docx2txt case-2025-0412-public.docm
$> cat case-2025-0412-public.docm.txt

Ravenbrook police
department
--------------------------------------------------------------------------------
Search and Seizure Warrant
Case No.: 2025-0412 Issued By: Swywy}wcm3U`}a{l2Hlpf`rm Date: 2025-05-01
Subject: Search and Seizure of Items Related to Unauthorized Cyber Intrusion
Suspect: A{nfu{>Yi}}j{ev Address: %#:2Iaqgdy~qdf-Sll25Zrlizu`b}q%>[P3.<#/
By order of the Ravenbrook Police Department, authorized officers have conducted a search of the above premises under suspicion of unlawful cyber activities, including but not limited to unauthorized access to protected systems.

The following items were seized as part of the investigation:
 Computers (one desktop and one laptop)
 An external harddrive labeled q~luaj*-:"#j!rs4v,k| <u-9'$wity8$9!.q
 USB Storage Devices
 Routers & Network Equipment
 Handwritten Notes & Documents Pertaining to Cyber Intrusion

All confiscated materials will be held at the Ravenbrook Police Department Cybercrime Division for forensic examination. Failure to comply with this warrant or obstruction of law enforcement officers will result in legal consequences.


Signed: Detective X~ddsh>Gm}idu`, Ravenbrook Police Department

Obtaining Application.UserName

From there you can extract some encrypted data. Paying attention in the VBA Script, we need the Application.UserName, for this task I thought into the metadata of the file, with exiftool we found the name: Olivia Renshaw:

$> exiftool case-2025-0412-public.docm
ExifTool Version Number         : 12.57
File Name                       : case-2025-0412-public.docm
Directory                       : .
File Size                       : 1722 kB
File Modification Date/Time     : 2025:05:09 01:47:38+02:00
File Access Date/Time           : 2025:08:12 23:57:31+02:00
File Inode Change Date/Time     : 2025:08:12 23:57:08+02:00
File Permissions                : -rwxrwxrwx
File Type                       : DOCM
File Type Extension             : docm
MIME Type                       : application/vnd.ms-word.document.macroEnabled.12
Zip Required Version            : 20
Zip Bit Flag                    : 0x0006
Zip Compression                 : Deflated
Zip Modify Date                 : 1980:01:01 00:00:00
Zip CRC                         : 0x07b58c22
Zip Compressed Size             : 426
Zip Uncompressed Size           : 1764
Zip File Name                   : [Content_Types].xml
Title                           :
Subject                         :
Creator                         : Olivia Renshaw
Keywords                        :
Description                     :
Last Modified By                : Olivia Renshaw
Revision Number                 : 2
Create Date                     : 2025:05:08 23:37:00Z
Modify Date                     : 2025:05:08 23:39:00Z
Template                        : Normal.dotm
Total Edit Time                 : 1 minute
Pages                           : 1
Words                           : 163
Characters                      : 933
Application                     : Microsoft Office Word
Doc Security                    : None
Lines                           : 7
Paragraphs                      : 2
Scale Crop                      : No
Company                         : Ravenbrook Police Department
Links Up To Date                : No
Characters With Spaces          : 1094
Shared Doc                      : No
Hyperlinks Changed              : No
App Version                     : 16.0000

Scripting

Knowing all of this, we create the following Python Script:

def decrypt(encrypted_text, key):
    decrypted = ""
    key_len = len(key)
    for i in range(len(encrypted_text)):
        key_index = (i + 1) % key_len
        key_char = key[key_index]
        original_char = chr(ord(encrypted_text[i]) ^ ord(key_char) ^ 123)
        decrypted += original_char
    return decrypted

key = "oliviarenshaw"
encrypted_strings = [
    "Swywy}wcm3U`}a{l2Hlpf`rm",
    "A{nfu{>Yi}}j{ev",
    "%#:2Iaqgdy~qdf-Sll25Zrlizu`b}q%>[P3.<#/",
    "q~luaj*-:\"#j!rs4v,k| <u-9'$wity8$9!.q",
    "X~ddsh>Gm}idu`"
]

for enc in encrypted_strings:
    print(decrypt(enc, key))

To obtain the flag:

$> python3 solver.py
Detective Olivia Renshaw
Victor Langford
217 Shadowcrest Ave, Ravenbrook, NX 4078
flag{c48219f5ea9d6bb54f7533edfc1a1124}
Olivia Renshaw

Common error

If you are trying by yourself and obtain a different output or the flag splitted, it may occur while copy and pasting the data, there is buggy byte: <0x7f>, I detected it with sublime text haha.

forensics/Bitlocked

Simple bitlocker bruteforce with an incompleted photo of the key recovery.

Mapping the device

As we have given an .001 file extension, we could use directly dislocker to the file but in that moment I colapsed and used: kpartx command to map the partitions from the disk image thus you can mount the device.

$> sudo kpartx -av SD_card.001
add map loop0p1 (254:0): 0 7854080 linear 7:0 8192

PD: It will be located in loop0p1 if you are using UEFI mode, else it will be on a sda1 directory

Dislocker

Knowing the dislocker arguments to decrypt the device with the Recovery Key, we are able to create a python bruteforce script:

sudo dislocker -V <device.dd>  -p<key> -- <Mount Point>

Bruteforce Script

Once this, create the script.

import subprocess
import os
import shutil

keyBase = "718894-682847-228371-253055-328559-381458-030668-04"
file = "/dev/mapper/loop0p1"
mntPoint = "/tmp/dislocker_test"

os.makedirs(mntPoint, exist_ok=True)

for i in range(10000, -1,-1):
    sufix = f"{i:04d}"
    key = f"{keyBase}{sufix}"
    print(f"Trying: {key}")

    for f in os.listdir(mntPoint):
        path = os.path.join(mntPoint, f)
        try:
            if os.path.isfile(path) or os.path.islink(path):
                os.unlink(path)
            elif os.path.isdir(path):
                shutil.rmtree(path)
        except Exception as e:
            pass

    result = subprocess.run(
        ["sudo", "dislocker", "-V", file, f"-p{key}", "--", mntPoint],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )

    if result.returncode == 0:
        print(f"Key found!: {key}")
        break

After some time bruteforcing from 049999 to 040000 we found the key was: 718894-682847-228371-253055-328559-381458-030668-047839

Opening autopsy

Finally with autopsy, or any related tool such as FTK Imager you will be able to locate flag.txt

forensics/ToShredYouSay

To be honest, I didn’t pwn this challenge it was my teammate tillamen from Caliphal Hounds. In the challenge they give you the following image:

And via the CTF logo you have to un-shred the image, so he printed the photo and do some manual art to obtain the following master piece:

Being the flag: flag{dd0755b73e4b7dfd0e06f927874e1511}. Congrats tillo!

forensics/The Wizard

File recognition

In this challenge a home directory is provided:

$> tar xvf thewizard.tgz
thewizard/
thewizard/.bashrc
thewizard/.bash_logout
thewizard/.pwfault
thewizard/.viminfo
thewizard/.profile

The only useful information was in .viminfo where it extract data from .pwfault to obtain the flag, anyways here the content of .viminfo:

# This viminfo file was generated by Vim 8.2.
# You may edit it if you're careful!

# Viminfo version
|1,4

# Value of 'encoding' when this file was written
*encoding=utf-8


# hlsearch on (H) or off (h):
~h
# Last Search Pattern:
~MSle0/?

# Last Substitute Search Pattern:
~MSle0~&.$

# Last Substitute String:
$}

# Command Line History (newest to oldest):
:q!
|2,0,1750169805,,"q!"
:%s/.$/}
|2,0,1750169794,,"%s/.$/}"
:%s/^...../flag{
|2,0,1750169789,,"%s/^...../flag{"
:s/dd/
|2,0,1750169783,,"s/dd/"
:%s/\n//
|2,0,1750169778,,"%s/\\n//"
:%s,[^a-f0-9],,g
|2,0,1750169774,,"%s,[^a-f0-9],,g"
::v/?/d
|2,0,1750169769,,":v/?/d"

# Search String History (newest to oldest):
? .$
|2,1,1750169794,,".$"
? ^.....
|2,1,1750169789,,"^....."
? dd
|2,1,1750169783,,"dd"
? \n
|2,1,1750169778,,"\\n"
? [^a-f0-9]
|2,1,1750169774,,"[^a-f0-9]"
? ?
|2,1,1750169769,,"?"

# Expression History (newest to oldest):

# Input Line History (newest to oldest):

# Debug Line History (newest to oldest):

# Registers:
""1 LINE  0
  [=Pb7F0=GP2%G(zi!%h::,zLl&
|3,1,1,1,1,0,1750169770,"[=Pb7F0=GP2%G(zi!%h::,zLl&"
"2  LINE  0
  wraC0,i0IkQN^,wIpKUgD:<g5#
|3,0,2,1,1,0,1750169770,"wraC0,i0IkQN^,wIpKUgD:<g5#"
"3  LINE  0
  ;0AHQQ8~<L{3^_z%/I/pkOdcSW
|3,0,3,1,1,0,1750169770,";0AHQQ8~<L{3^_z%/I/pkOdcSW"
"4  LINE  0
  ml7@zu^fbae:OG7,{jPiiq[H3-
|3,0,4,1,1,0,1750169770,"ml7@zu^fbae:OG7,{jPiiq[H3-"
"5  LINE  0
  IkW>#H>YXf>;\=S-Sa7aup=d3k
|3,0,5,1,1,0,1750169770,"IkW>#H>YXf>;\\=S-Sa7aup=d3k"
"6  LINE  0
  y7q+01%Tk"V$#I_FK5ST;&Q$WE
|3,0,6,1,1,0,1750169770,"y7q+01%Tk\"V$#I_FK5ST;&Q$WE"
"7  LINE  0
  J&<ou+;Q+5NCVI=i.mt^KRvvUE
|3,0,7,1,1,0,1750169770,"J&<ou+;Q+5NCVI=i.mt^KRvvUE"
"8  LINE  0
  eE.u\Hg6"#Mem:4reCy[QB;:9K
|3,0,8,1,1,0,1750169770,"eE.u\\Hg6\"#Mem:4reCy[QB;:9K"
"9  LINE  0
  Y{zM<!*32kpTPJ`R7bHUV~]3O$
|3,0,9,1,1,0,1750169770,"Y{zM<!*32kpTPJ`R7bHUV~]3O$"

# File marks:
'0  1  0  ~/.pwfault
|4,48,1,0,1750169805,"~/.pwfault"

# Jumplist (newest first):
-'  1  0  ~/.pwfault
|4,39,1,0,1750169805,"~/.pwfault"

# History of marks within files (newest to oldest):

> ~/.pwfault
  * 1750169794  0
  " 1 0
  . 1 0
  + 2 0
  + 1 7
  + 2 0
  + 1 0

Understanding .viminfo logic

The vim file follows this steps sequece, based in the command timestamps:

  1. :v/?/d -> delete lines NOT matching “?” (i.e., keep only lines containing ‘?’)
  2. :%s,[^a-f0-9],,g -> remove all characters not a-f or 0-9 (global)
  3. :%s/\n// -> remove all newlines (join lines)
  4. :s/dd/ -> remove the first occurrence of ‘dd’ on each line (here file is single line by now)
  5. :%s/^…../flag{ -> replace first 5 characters with ‘flag{’
  6. :%s/.$/} -> replace last character of the file with ‘}’

Scripting the logic

Thus create a Python Script:

import re 

pwfault = open('.pwfault','r',encoding='utf-8', errors='ignore').read()

lines = pwfault.splitlines()
lines = [ln for ln in lines if '?' in ln]

step1 = "\n".join(lines)
step2 = re.sub(r'[^a-f0-9]', '', step1)
step3 = step2.replace("\n", "")
step4 = step3.replace("dd", "", 1)

if len(step4) >= 5:
    step5 = 'flag{' + step4[5:]
else:
    step5 = 'flag{'

if len(step5) >= 1:
    final = step5[:-1] + '}'
else:
    final = '}'

print("Lines kept (contain '?'):\n", step1, "\n")
print("After removing non-hex chars:\n", step2, "\n")
print("After joining lines (no newlines):\n", step3, "\n")
print("After removing first 'dd' occurrence:\n", step4, "\n")
print("After replacing first 5 chars with 'flag{':\n", step5, "\n")
print("Final result after replacing last char with '}':\n", final, "\n")

hex_candidate = re.fullmatch(r'[0-9a-f]+', final)
if hex_candidate:
    try:
        decoded = bytes.fromhex(final).decode('utf-8')
        print("Decoded from hex ->", decoded)
    except Exception as e:
        print("Could not decode hex to UTF-8:", e)

Having the following output:

$> python3 solver.py
Lines kept (contain '?'):
 f)#-nA1~wex3z$Ix0*K^a0?ImR
.!Y}h.`:?u\GfM@6?d,]pZGdT>
jeZ!BS{Ur?pxt*q@$.?\S+jJKy
Vz#@^Fw?#Up|@LF8SrkL3UM[nX
OBk?"Ovlek;p1/lz$=iN(h./fI
P?1=C8Ud$Z-Ew;+z0Ap/shp4oc
W&k]&([X>tz*:1UTRZfs?Tg-sA
-CVwhU!L9C`}?K>W)!'-("Jmh"
XEjZ3V8M$3.vaFA30CB?DT7EDs
_5{1-%M:gl?^kO[%1vUI*WCBFV
6Q`$%_O/8wSHOewxq/p?|+~:9h

After removing non-hex chars:
 f1e30a0f6dde83e1f18d04c1f9383a30751168e9

After joining lines (no newlines):
 f1e30a0f6dde83e1f18d04c1f9383a30751168e9

After removing first 'dd' occurrence:
 f1e30a0f6e83e1f18d04c1f9383a30751168e9

After replacing first 5 chars with 'flag{':
 flag{a0f6e83e1f18d04c1f9383a30751168e9

Final result after replacing last char with '}':
 flag{a0f6e83e1f18d04c1f9383a30751168e}

Honestly a beautiful output for a second blood :)

forensics/IOT breach

Device recognition

Disk forensics! A DOS/MBR boot file with some ransomware attack. This time we will be using Autopsy to read the device content. At first glance, files directory is listed on the root system / with an index of .enc jpg images.

lighttpd access.log

As we didn’t find any suspicious file in /home and /root directories, and nothing relevant inside the bash history, I move on into services log, here the http service: lighttpd was interesting. In /var/log/lighttpd/access.log some base64 data was embedded in http requests.

Perl Script

With some regex filtering and cyberchef we obtain a base64 encrypyted payload:

From this perl code we obtain the cypher method and an IV

my $cipher = Crypt::Mode::CBC->new("AES");
my $encrypted = $cipher->encrypt($data, $password, "R4ND0MivR4ND0Miv");

Perl Execution | CBC key

Flexing my custom command we urldecode one http request with the $password used in the perl binary:

$> urldecode '10.0.0.1 10.5.5.246 - [11/Jun/2025:01:13:02 +0200] "GET /cgi-bin/ping.pl?hostname=a%3Bcd%20/files%20%26%26%20perl%20enc.pl%20L0s3%40llYourF1l3s HTTP/1.1" 200 1677 "-" "python-requests/2.21.0"'
10.0.0.1 10.5.5.246 - [11/Jun/2025:01:13:02 +0200] "GET /cgi-bin/ping.pl?hostname=a;cd /files && perl enc.pl L0s3@llYourF1l3s HTTP/1.1" 200 1677 "-" "python-requests/2.21.0"
urldecode () {
        echo -n $@ | python3 -c "import sys; from urllib.parse import unquote; print(unquote(sys.stdin.read()));"
}

Being L0s3@llYourF1l3s the key from AES CBC.

Decrypting files

To conclude the challenge, you can create a fast bash script to decrypt all encoded files inside /files/ directory.

#!/bin/bash

PASS="L0s3@llYourF1l3s"
IV="R4ND0MivR4ND0Miv"

for i in $(seq -w 1 16); do
    INFILE="kitten${i}.jpg.enc"
    OUTFILE="kitten${i}.jpg"

    if [[ -f "$INFILE" ]]; then
        echo "[*] Decrypting $INFILE -> $OUTFILE"
        openssl enc -d -aes-128-cbc \
            -in "$INFILE" \
            -out "$OUTFILE" \
            -K $(echo -n "$PASS" | xxd -p) \
            -iv $(echo -n "$IV" | xxd -p)
    else
        echo "[!] $INFILE does not exist, skipping..."
    fi
done

The file kitten08.jpg was the flag written.

Reversing Index

Sad ending, I still asking to organizers ctf for reversing files, but I don’t have any file from the category, only some solvers that I will share with you :). Anyways sorry for the bad writeups you will see forward, but I need to introduce reversing content in my web, at least on ctf blogs.

reversing/Lazy Code 1.0

From IDA we extracted the following loop-code and function:

Main code

for ( i = 1; i <= 1000; ++i )
  {
    printf("[+] Decrypting step %d/1000....\n", i);
    xor_string(encrypted_flag, xors[i % 0x1BuLL]);
    printf("[!] Yawn.... I'm tired... sleeping for %d seconds\n", sleeping_time);
    sleep(sleeping_time);
  }
  printf("Pfff... I'm done, here is your flag: %s\n", encrypted_flag);
  return 0;
}
_BYTE *__fastcall xor_string(__int64 a1, char a2)
{
  _BYTE *result; // rax
  int i; // [rsp+Ch] [rbp-4h]

  for ( i = 0; i <= 38; ++i )
  {
    result = (_BYTE *)(i + a1);
    *result ^= a2;
  }
  return result;
}

Data used in main code

Having the encrypted_flag value:

.data:0000000140003000 encrypted_flag  db 15h, 1Fh, 12h, 14h, 8, 10h, 'J', 'E', 'C', 15h, 4 dup('F')
.data:0000000140003000                                         ; DATA XREF: main+7Fo
.data:0000000140003000                                         ; main+C3o
.data:000000014000300E                 db 'G', 'K', 'D', 'B', 15h, 2 dup(16h), 2 dup(17h), 'D'
.data:0000000140003018                 db 10h, 2 dup('@'), 12h, 'A', 'C', 15h, 11h, 'D', '@'
.data:0000000140003022                 db 'J', 'B', 'J', 0Eh, 's', 19h dup(0)

And xors value:

.data:0000000140003040 xors            dd 12h, 2Bh, 47h, 76h, 66h, 4, 0Bh, 36h, 21h, 6Ch, 37h
.data:0000000140003040                                         ; DATA XREF: main+6Do
.data:000000014000306C                 dd 31h, 1Bh, 5Bh, 13h, 67h, 43h, 1Eh, 9, 27h, 8, 79h, 4Bh
.data:000000014000309C                 dd 0Dh, 3Dh, 52h, 5Bh

Solver written in C

We are able to create the following script to recreate the function functionality without having to wait any second..

#include <stdio.h>

unsigned char encrypted_flag[39] = {
    0x15, 0x1F, 0x12, 0x14, 0x08, 0x10, 0x4A, 0x45, 0x43, 0x15,
    0x46, 0x46, 0x46, 0x46, 0x47, 0x4B, 0x44, 0x42, 0x15, 0x16,
    0x16, 0x17, 0x17, 0x44, 0x10, 0x40, 0x40, 0x12, 0x41, 0x43,
    0x15, 0x11, 0x44, 0x40, 0x4A, 0x42, 0x4A, 0x0E, 0x73
};

char xors[27] = {
    0x12, 0x2B, 0x47, 0x76, 0x66, 0x04, 0x0B, 0x36, 0x21, 0x6C,
    0x37, 0x31, 0x1B, 0x5B, 0x13, 0x67, 0x43, 0x1E, 0x09, 0x27,
    0x08, 0x79, 0x4B, 0x0D, 0x3D, 0x52, 0x5B
};

void xor_string(unsigned char *str, char key) {
    for (int i = 0; i <= 38; ++i) {
        str[i] ^= key;
    }
}

int main() {
    for (int i = 1; i <= 1000; ++i) {
        xor_string(encrypted_flag, xors[i % 27]);
    }
    printf("Pfff... I'm done, here is your flag: %s\n", encrypted_flag);
    return 0;
}

reversing/Lazy Code 2.0

Same stuff than the first part but with different encrypted_flag value:

Main code

int __fastcall main(int argc, const char **argv, const char **envp)
{
  int i; // [rsp+8h] [rbp-8h]

  for ( i = 1; i <= 1000; ++i )
  {
    printf("[+] Decrypting step %d/1000....\n", i);
    xor_string(encrypted_flag, xors[i % 0x1BuLL]);
    printf("[!] Yawn.... I'm tired... sleeping for %d seconds\n", sleeping_time);
    sleep(sleeping_time);
  }
  printf("Pfff... I'm done, here is your flag: %s\n", encrypted_flag);
  return 0;
}
_BYTE *__fastcall xor_string(__int64 a1, char a2)
{
  _BYTE *result; // rax
  int i; // [rsp+18h] [rbp-4h]

  for ( i = 0; i <= 38; ++i )
  {
    result = (_BYTE *)(i + a1);
    *result ^= a2;
  }
  return result;
}

Encrypted Flag data

.data:0000000000004020 encrypted_flag  db '* -+7.'             ; DATA XREF: main+83o
.data:0000000000004020                                         ; main+CCo
.data:0000000000004026                 db  7Fh ; 
.data:0000000000004027                 db  2Ah ; *
.data:0000000000004028                 db  28h ; (
.data:0000000000004029                 db  2Dh ; -
.data:000000000000402A                 db  78h ; x
.data:000000000000402B                 db  7Dh ; }
.data:000000000000402C                 db  7Ah ; z
.data:000000000000402D                 db  28h ; (
.data:000000000000402E                 db  2Dh ; -
.data:000000000000402F                 db  29h ; )
.data:0000000000004030                 db  2Eh ; .
.data:0000000000004031                 db  28h ; (
.data:0000000000004032                 db  29h ; )
.data:0000000000004033                 db  2Ah ; *
.data:0000000000004034                 db  7Bh ; {
.data:0000000000004035                 db  2Dh ; -
.data:0000000000004036                 db  79h ; y
.data:0000000000004037                 db  28h ; (
.data:0000000000004038                 db  7Bh ; {
.data:0000000000004039                 db  75h ; u
.data:000000000000403A                 db  28h ; (
.data:000000000000403B                 db  29h ; )
.data:000000000000403C                 db  2Eh ; .
.data:000000000000403D                 db  7Ch ; |
.data:000000000000403E                 db  7Bh ; {
.data:000000000000403F                 db  2Fh ; /
.data:0000000000004040                 db  78h ; x
.data:0000000000004041                 db  7Fh ; 
.data:0000000000004042                 db  7Fh ; 
.data:0000000000004043                 db  7Bh ; {
.data:0000000000004044                 db  79h ; y
.data:0000000000004045                 db  31h ; 1
.data:0000000000004046                 db  4Ch ; L
.data:0000000000004047                 db    0
.data:0000000000004048                 db    0
.data:0000000000004049                 db    0
.data:000000000000404A                 db    0
.data:000000000000404B                 db    0
.data:000000000000404C                 db    0
.data:000000000000404D                 db    0
.data:000000000000404E                 db    0
.data:000000000000404F                 db    0
.data:0000000000004050                 db    0
.data:0000000000004051                 db    0
.data:0000000000004052                 db    0
.data:0000000000004053                 db    0
.data:0000000000004054                 db    0
.data:0000000000004055                 db    0
.data:0000000000004056                 db    0
.data:0000000000004057                 db    0
.data:0000000000004058                 db    0
.data:0000000000004059                 db    0
.data:000000000000405A                 db    0
.data:000000000000405B                 db    0
.data:000000000000405C                 db    0
.data:000000000000405D                 db    0
.data:000000000000405E                 db    0
.data:000000000000405F                 db    0

Xor keys data

.data:0000000000004060                 public xors
.data:0000000000004060 ; unsigned int xors[27]
.data:0000000000004060 xors            dd 17h, 2Bh, 57h, 6Fh, 7Ah, 0, 21h, 2Dh, 1Dh, 67h, 2Bh
.data:0000000000004060                                         ; DATA XREF: main+71o
.data:000000000000408C                 dd 56h, 16h, 63h, 5Bh, 67h, 38h, 1Dh, 0Bh, 0Ah, 3, 70h
.data:00000000000040B8                 dd 47h, 12h, 36h, 0Ch, 63h

Solver written in C

#include <stdio.h>

unsigned char encrypted_flag[39] = {
    '*', ' ', '-', '+', '7', '.', 0x7F, '*', '(', '-', 'x', '}', 'z', '(', '-', ')', '.', '(', ')', '*', '{', '-', 'y', '(', '{', 'u', '(', ')', '.', '|', '{', '/', 'x', 0x7F, 0x7F, '{', 'y', '1', 'L'
};

char xors[27] = {
    0x17, 0x2B, 0x57, 0x6F, 0x7A, 0x00, 0x21, 0x2D, 0x1D, 0x67, 0x2B, 0x56, 0x16, 0x63, 0x5B, 0x67, 0x38, 0x1D, 0x0B, 0x0A, 0x03, 0x70, 0x47, 0x12, 0x36, 0x0C, 0x63
};

void xor_string(unsigned char *str, char key) {
    for (int i = 0; i <= 38; ++i) {
        str[i] ^= key;
    }
}

int main() {
    for (int i = 1; i <= 1000; ++i) {
        xor_string(encrypted_flag, xors[i % 27]);
    }
    printf("Pfff... I'm done, here is your flag: %s\n", encrypted_flag);
    return 0;
}

reversing/3 Ball Mark

Those who lie to others deceive themselves. I completed this challenge doing a manual bruteforce I tried 2 times to guess a number between 1 and 3, 10 times in a row, and obtained the flag, because it was always the same pattern. The solver of this challenge was literaly a bruteforce of the digits.

The 100 easiest points of my career


Conclusion

I hope you liked and enjoyed the writeups, see you in others CTFs playing with Caliphal Hounds! (and who knows maybe with Lil L3ak…)